Markdownで設計を書いてみよう

![banner-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3827848/7244848a-dc98-9fcf-401a-2dbe79a534df.png)


# はじめに

はじめまして、ポーラ・オルビスホールディングスで内製エンジニアをやっている高田です。

これまでExcelで設計書を作成するプロジェクトに多く関わってきましたが、
Markdownとmermaidを使えばもっと楽に設計できるのではないかと思い、設計書のMarkdown化を進めてみました。
そこで見えてきた良い点や課題を共有したいと思います。

この記事ではMarkdownの設計書についてをテーマに、実際にMarkdownの設計を書いてみたいと思います。

# Excel で設計書を書くのって…

「はじめに」で触れたとおり、経験則から言うと Excel で設計書を管理しているプロジェクトはとても多いです。

Excel 設計書のメリットは以下のような感じで

- Office ソフトさえ入っていれば誰でも編集と閲覧ができる
- フォーマットの自由度が高い
- 関数やマクロが使える

要はプロジェクトや開発環境を選ばずに採用できるという強み故、
多くのプロジェクトがExcel設計書を採用しているのだろうと思います。

反面、エンジニア視点では以下のような課題も感じていました

- 体裁調整に時間がかかる（罫線の切れやフォントの統一など）
- 独自フォーマットになりやすい（Excel方眼紙によるフリーフォーマット化など）
- 変更管理が手作業になりがち

要は「どんなプロジェクトにも採用しやすいけど、綺麗で見やすいドキュメントを管理するにはそれなりの労力がかかる」
っていうのが Excel 設計書の特徴かなと感じていました。

# 課題解決のために…

上記の課題を解決するために、「そもそも Excel 設計書からいったん離れてみたらどうだろう？」と考えました。
そこで目を付けたのが Markdown + git (+ mermaid + draw.io) での設計書管理でした。

それぞれの課題へのアプローチは以下の通りです。


>体裁調整に時間がかかる

→ Markdown で記載することで体裁崩れを防止
Markdown はシンプルなテキスト形式であり、フォーマットが統一されているため、体裁調整にかかる時間を大幅に削減できます。


> 独自フォーマットになりやすい

→ Markdown + mermaid でフォーマットの独自化を防止
Markdown と mermaid を使用することで、標準化されたフォーマットを維持できます。
特にシーケンス図などの作図が必要な場合は UML に準拠することで、さらに統一感を持たせることができます。


> 変更管理が手作業になりがち

→ git による管理とチケット駆動開発と組み合わせることで証跡をチケットに紐づけて管理しやすく
git を使用することで、変更履歴を自動的に管理できます。
また、チケット駆動開発と組み合わせることで、変更内容をチケットに紐づけて管理することができ、証跡の追跡が容易になります。


これらのアプローチにより、前述の課題を解決することができました。

# Markdown で書いた設計書のサンプル

ここからは Markdown で書いた設計書のサンプルを載せていこうと思います。

Qiita 自体が Markdown 記法で書かれてるのでどう表現しようか迷いました。

画面設計についてはワイヤーフレーム部分でdraw.ioを使ったので、画像とテキストベースの情報を直接記載し、
draw.ioを使用しないAPI設計とDB設計についてはコードブロックで記載していこうと思います。

## 例えばこういうシステムがあったとして…

まずは以下のような構成の家計簿管理のシステムを仮定しましょう。

仮のシステムといったら家計簿ですね。
私たちは家計の管理が大好き。

```mermaid

flowchart LR
  front["webサイト
  (AWS Amplify)"]
  api_put["家計簿登録API
  (AWS API Gateway)"]
  api_get["家計簿取得API
  (AWS API Gateway)"]
  lambda_put["家計簿登録処理
  (AWS Lambda)"]
  lambda_get["家計簿取得処理
  (AWS Lambda)"]
  rds["家計簿管理DB
  (AWS Aurora)"]

  front --- api_put --- lambda_put --- rds
  front --- api_get --- lambda_get --- rds

```

今回は上記の構成の家計簿管理システムについて設計を作ってみます。
詳細設計レベルの情報は今回は割愛し、基本設計レベルの情報について例示していきたいと思います。

今回例示するものは以下の通りとなります。

- 画面設計
  - 画面のワイヤーフレームや画面遷移の情報が記載されているもの
- API 設計
  - IF 定義 や API のパス等について定義したもの
- DB 設計
  - RDBMS の種類とバージョン、DB 名、テーブル定義について記載したもの

フロントエンドを実装するうえでのコンポーネントやステート管理、ルーティングであったり、
Lambda の処理フローについては今回は詳細設計レベルとして割愛させていただきました。

## 画面設計の例

以下は画面設計の例です。draw.ioで作った簡素なワイヤーフレームと、
Markdownで書いたテキストベースの情報を記載しました。

---

**画面設計書**

ワイヤーフレーム

![ワイヤーフレーム_サンプル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3827848/ad44978a-541a-f3b3-2ab4-65d21af8e98e.png)



画面の構成要素

| グループ   | 項目名                   | 用途                                                           |
| ---------- | ------------------------ | -------------------------------------------------------------- |
| 家計簿入力 | 日付選択カレンダー       | 帳簿の日付を指定する                                           |
|            | 収入・支出選択プルダウン | 収入か支出かを選択する                                         |
|            | カテゴリ選択プルダウン   | 収入、支出のうちの分類を指定する                               |
|            | 金額入力テキストボックス | 金額を入力する                                                 |
|            | 保存ボタン               | 入力した家計を保存する。「入力情報保存」のイベントを実行する。 |
| 家計簿閲覧 | 表示月選択プルダウン     | 表示したい年月を選択する                                       |
|            | 表示ボタン               | 選択し年月の家計簿を表示する。「家計表示」のイベントを実行する |
|            | 家計簿表示欄             |                                                                |

実行されるイベント

- 入力情報保存
  - 家計簿登録 API を実行する
  - 収入・支出入力フォーム で入力した内容をリクエストボディとする
- 家計表示
  - 家計簿取得 API を実行する
  - 「表示する月」の内容をクエリパラメータとする

---

・・・

今回の例では簡素なワイヤーフレームの作図と、画面の構成要素の列挙としました。
今回は1画面なので遷移図等はないですが、
複数画面が存在する場合は画面遷移図や画面ごとのルーティング設定があるといいと思います。

ちなみに今回は drawio を使ってみましたが、
draw.io は `*.drawio.png` の拡張子で保存することで draw.io で編集したファイルを そのまま png として扱うことができます。
もちろんそのまま Markdown に埋め込むこともできます。

ただ、上記の場合は `*.dio` や `*.drawio` で保存したときと違い、
draw.io の図の中の文字列を grep で検索できなくなったりgit 管理に乗せる際も差分が見れなくなります。

1 枚の draw.io ファイルの粒度や情報量の違いで上記の拡張子の選定も変わってくるかと思いますので、
プロジェクトの特性に合った方針を取っていただけるとよいかなと思います。

## API 設計の例

以下では API 設計の例を記載します。

今回の例ではパスパラメータを使う API はないですが、
パスパラメータを使う場合は `API パス`の欄を`/kakeibo/{id}`のように表記します。

なお、リクエストボディにおいてはプロパティの省略は許容しない基本方針としています。
プロパティの省略がある場合は`リクエストボディ`の中に省略可否の記載を追加する必要があります。

---

### 家計簿取得 API の例

以下は家計簿データの取得 API の例です。

````Markdown
# API 設計書

- [API 設計書](#api-設計書)
- [概要](#概要)
- [インターフェース仕様](#インターフェース仕様)
  - [API パス](#api-パス)
  - [メソッド](#メソッド)
  - [クエリパラメータ](#クエリパラメータ)
  - [リクエストボディ](#リクエストボディ)
  - [レスポンス](#レスポンス)
    - [ステータスコード](#ステータスコード)
    - [レスポンスボディ](#レスポンスボディ)
- [CRUD 表](#crud-表)

# 概要

家計簿データの取得を行います。AWS の APIGateway と Lambda 関数を使用して実装します。

# インターフェース仕様

## API パス

- `/kakeibo`

## メソッド

- `GET`

## クエリパラメータ

- `month`: 取得する月（例: `2023-10`）

## リクエストボディ

なし

## レスポンス

### ステータスコード

- `200 OK`: 正常なレスポンス
- `400 Bad Request`: パラメータエラー
- `500 Internal Server Error`: サーバーエラー

### レスポンスボディ

```json
  {
    "data": [
      {
        "date": "2023-10-01", // 日付(文字列/notnull)
        "type": "income", // 収入・支出(文字列/notnull)
        "category": "salary", // カテゴリ(文字列/notnull)
        "amount": 999999 // 金額(数値/notnull)
      },
      ...
    ]
  }
```

# CRUD 表

| テーブル論理名 | テーブル物理名 | Create | Read | Update | Delete |
| -------------- | -------------- | ------ | ---- | ------ | ------ |
| 家計簿         | KAKEIBO        |        | ○    |        |        |

````

---

### 家計簿登録 API の例

以下は家計簿登録 API の設計の例です。

````Markdown
# API 設計書

- [API 設計書](#api-設計書)
- [概要](#概要)
- [インターフェース仕様](#インターフェース仕様)
  - [API パス](#api-パス)
  - [メソッド](#メソッド)
  - [リクエスト](#リクエスト)
    - [クエリパラメータ](#クエリパラメータ)
    - [リクエストボディ](#リクエストボディ)
  - [レスポンス](#レスポンス)
    - [ステータスコード](#ステータスコード)
    - [レスポンスボディ](#レスポンスボディ)
- [CRUD 表](#crud-表)

# 概要

家計簿データの登録を行います。AWS の APIGateway と Lambda 関数を使用して実装します。

# インターフェース仕様

## API パス

- `/kakeibo`

## メソッド

- `POST`

## リクエスト

### クエリパラメータ

なし

### リクエストボディ

```json
{
  "date": "2023-10-01", // 日付(文字列/notnull)
  "type": "income", // 収入・支出(文字列/notnull)
  "category": "salary", // カテゴリ(文字列/notnull)
  "amount": 1000 // 金額(数値/notnull)
}
```

## レスポンス

### ステータスコード

- `200 OK`: 正常なレスポンス
- `400 Bad Request`: パラメータエラー
- `500 Internal Server Error`: サーバーエラー

### レスポンスボディ

```json
{
  "message": "Record added successfully"
}
```

# CRUD 表

| テーブル論理名 | テーブル物理名 | Create | Read | Update | Delete |
| -------------- | -------------- | ------ | ---- | ------ | ------ |
| 家計簿         | KAKEIBO        | ○      |      |        |        |

````

上記のようにリクエストボディとレスポンスボディをコードブロックで書くのは 
IF が明確になって結合時のバグを減らせるのが個人的には良い感じでした。

反面、型や null 可否などをコメント形式で埋め込んでしまっているので、複雑な型に対応しづらい面もあります。


## DB 設計の例

以下は DB 設計の例です。
当初は家計簿情報テーブルの 1 テーブルの想定でしたが、
ER 図を載せるためにユーザーテーブルを追加しています。

````Markdown
# DB設計書

- [データベース情報](#データベース情報)
- [テーブル定義](#テーブル定義)
  - [ユーザー情報テーブル](#ユーザー情報テーブル)
  - [家計簿情報テーブル](#家計簿情報テーブル)
- [ER 図](#er-図)

# データベース情報

- **DB 論理名**: 家計簿データベース
- **DB 物理名**: kakeibo_db
- **ホスト名** : kakeibo_db_host
- **RDBMS の種類とバージョン**: PostgreSQL 13

# テーブル定義

## ユーザー情報テーブル

- **論理名**: ユーザー情報
- **物理名**: users

| カラム物理名 | カラム論理名   | データ型  | 凡例                        | FK  | PK  | ユニーク制約 |
| ------------ | -------------- | --------- | --------------------------- | --- | --- | ------------ |
| user_id      | ユーザー ID    | SERIAL    | 1                           |     | ○   | ○            |
| username     | ユーザー名     | VARCHAR   | 情報太郎                    |     |     | ○            |
| email        | メールアドレス | VARCHAR   | info_taro@example.dummy |     |     | ○            |
| created_at   | 作成日時       | TIMESTAMP | 2023-01-01 00:00:00         |     |     |              |

## 家計簿情報テーブル

- **論理名**: 家計簿情報
- **物理名**: kakeibo

| カラム物理名 | カラム論理名 | データ型  | 凡例                | FK  | PK  | ユニーク制約 |
| ------------ | ------------ | --------- | ------------------- | --- | --- | ------------ |
| record_id    | 記録 ID      | SERIAL    | 1001                |     | ○   | ○            |
| user_id      | ユーザー ID  | INTEGER   | 1                   | ○   |     |              |
| date         | 日付         | DATE      | 2023-10-01          |     |     |              |
| type         | 収入・支出   | VARCHAR   | income              |     |     |              |
| category     | カテゴリ     | VARCHAR   | salary              |     |     |              |
| amount       | 金額         | INTEGER   | 1000                |     |     |              |

# ER 図

```mermaid
erDiagram
    USERS {
        SERIAL user_id PK
        VARCHAR username
        VARCHAR email
        TIMESTAMP created_at
    }
    KAKEIBO {
        SERIAL record_id PK
        INTEGER user_id FK
        DATE date
        VARCHAR type
        VARCHAR category
        INTEGER amount
    }
    USERS ||--o{ KAKEIBO : "has"
```

````

埋め込んだ ER 図は実際は以下のように見えます。

```mermaid
erDiagram
    USERS {
        SERIAL user_id PK
        VARCHAR username
        VARCHAR email
        TIMESTAMP created_at
    }
    KAKEIBO {
        SERIAL record_id PK
        INTEGER user_id FK
        DATE date
        VARCHAR type
        VARCHAR category
        INTEGER amount
    }
    USERS ||--o{ KAKEIBO : "has"
```

ちなみに上記の ER 図については、テーブルの設計が完了した後に github copilot を使って生成しています。
このように copilot を活用しやすいことも、Markdown 設計のメリットの一つです。

# gitに乗せてみよう

設計書が書けたらあとはgitに乗せましょう。

githubと連携していれば、通常のreadme.mdと同じようにgithub上でMarkdownのプレビューが参照できます。
もちろんmermaidで書いた図も以下のように参照できます。

![gitキャプチャ_プレビュー.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3827848/f9540d9c-b2f6-d0cf-e40b-40e793418f64.png)


また、PRを出した際は差分もプレビューできます。

![gitキャプチャ_PR.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3827848/fb414f20-a3aa-aedd-324c-c27d56af39e1.png)

上記は今回書いたDB設計にカテゴリマスタを追加した例です。
コードの差分表記と同じで追記した箇所は緑、削除した箇所は赤で表示されています。

設計書の差分が一目でわかるのでレビューや変更管理の効率を向上することができます。

チケット駆動型の開発などではチケット単位でfearureブランチを切るため、
差分をソースコードと照らし合わせて確認しやすいのもメリットに感じました。

# まとめ

ここまで仮のシステムを例にとり Markdown の設計書を作ってみて、以下のようなメリットを感じました。

- 証跡が管理しやすい、チケット駆動開発との相性がいい
- 設計から開発まで VScode などのエディタの中で完結できる
- 体裁崩れを気にしないで済む
- AI の活用がしやすい

反面、以下のような弱みも感じました

- DB のテーブル設計などについては、セル形式の Excel に一日の長がある
- チームの外部とやり取りする際に一旦 pdf に出力するなどの対応が必要になる

技術者視点では嬉しいことの多い Markdown 設計でしたが、
チーム外への連携等を考慮すると面倒なことも多いという感じでした。

上記の強みと弱みを評価して、プロジェクトやチームに合った設計方針を選定することが大切かと思います。
この記事がその一助になることを願います。