はじめに
OpenAPIを用いてAPIファーストな開発を行っているとモックサーバーやコードの自動生成、テストの自動化など多くのメリットがあります。そうすると徐々に設計ドキュメントも一箇所にまとめたい欲求にかられます。
もやもやポイント
- ドキュメントの乖離 実装変更時にOpenAPIまたは設計書の更新が漏れる
- 情報の分散 API仕様だけでは詳細が理解できなかった際に複数のドキュメントを行き来する
- メンテナンスコストの増加 複数のドキュメントを同期させる運用負荷がかかる
これらのもやもやを解消するためにOpenAPIに設計情報も記述することで次のような効果を得られます。
- Gitによるバージョン管理やレビューの恩恵を受ける。
- 設計書の整形と闘うことなく既存ツールだけできれいなフォーマットで閲覧できる。
- ドキュメントが一元管理され、チームメンバーには「OpenAPIを見て」と伝えるだけですむ。
いいことづくめですね。
本記事ではOpenAPIのpathsのdescriptionフィールドにMarkdownで設計を記述しOpenAPIに設計情報も集約します。Single Source of Truthってやつを目指します。
前提知識
- OpenAPI Specification 3.0以上の基本
- REST APIの基礎
- Markdownの基本記法
本記事で扱うこと
- OpenAPIのdescriptionフィールドに何を書くか
- Mermaidを用いてリッチに表現する
本記事が適さない方
OpenAPIに設計を含むため外部公開するAPI開発には向きません。
このアプローチは非公開APIの開発チームを対象にしています。
また、本記事ではAPIファーストな開発のあれこれは取り扱いません。
どこまで書いたらいいですか?
公式サイトに記載の通り、descriptionにCommonMarkが使えます。
Paths may have an optional short summary and a longer description for documentation purposes. This information is supposed to be relevant to all operations in this path. description can be multi-line and supports Markdown (CommonMark) for rich text representation.
エンドポイント(paths)ごとに設計をまとめましょう、でこの記事終わってしまいますが次に設計をどこまで書くべきでしょうか?という声が聞こえてきます。段階的なテンプレートを用意したのでプロジェクトの状態や実装の複雑さに応じて選択してください。
- レベル1 基本的な機能説明
- レベル2 処理フロー+レスポンスのデータマッピング
- レベル3 図を用いた詳細な設計
レベル1 基本的な機能説明
シンプルなCRUDや最低限の機能内容を伝えます。
テンプレート
get:
summary: [機能名を記述]
description: |
## 機能概要
- [機能の概要1]
- [機能の概要2]
- [機能の概要3]
## 注意事項
- [注意点1]
- [注意点2]
記述例
get:
summary: ユーザー一覧取得
description: |
## 機能概要
- ページネーション対応のユーザー一覧を取得します。
- 検索キーワードを指定してユーザー名で絞り込むことができます。
## 注意事項
- デフォルトでは20件ずつ取得されます
- 最大取得件数は100件までです
レベル2 処理フロー+レスポンスのデータマッピング
データのIN/OUTを明確にしてビジネスロジックを含めた情報を提供します。
テンプレート
get:
summary: [機能名を記述]
description: |
## 機能概要
- [機能の概要1]
- [機能の概要2]
- [機能の概要3]
## 処理フロー
1. [ステップ1]
2. [ステップ2]
3. [ステップ3]
## データ取得SQL(該当する場合)
```sql
SELECT
[カラム1],
[カラム2]
FROM [テーブル名]
WHERE [条件]
LIMIT $1 OFFSET $2;
```
## マッピング仕様(該当する場合)
| DBカラム | レスポンスフィールド | 変換ルール |
|---------|-------------------|-----------|
| [column_name] | [fieldName] | [変換方法] |
## エラーケース
- [エラーシナリオ1]: [HTTPステータス]
- [エラーシナリオ2]: [HTTPステータス]
記述例
get:
summary: ユーザー一覧取得
description: |
## 機能概要
ユーザー一覧を取得します。
- ページネーションに対応します。
- 検索キーワードを指定してユーザー名で絞り込むことができます。
## 処理フロー
1. パラメータのバリデーション(page、limit、keyword)
2. キャッシュ確認(Redis)
3. データ取得(ページネーション対応)
4. 総件数取得(ページネーション情報算出)
5. レスポンス整形
6. キャッシュ保存(TTL: 5分)
## データ取得SQL
```sql
SELECT
u.user_id,
u.username,
u.email,
u.display_name,
u.created_at
FROM users u
WHERE u.deleted_at IS NULL
AND (u.username LIKE $1 OR $1 IS NULL)
ORDER BY u.created_at DESC
LIMIT $2 OFFSET $3;
```
## マッピング仕様
| DBカラム | レスポンスフィールド | 変換ルール |
|---------|-------------------|-----------|
| user_id | userId | |
| username | username | |
| email | email | |
| display_name | displayName | キャメルケース変換 |
| created_at | createdAt | ISO 8601形式 |
## エラーケース
- pageまたはlimitが範囲外の場合: 400 Bad Request
- データベース接続エラー: 500 Internal Server Error
レベル3 図を用いた詳細な設計
複雑なビジネスロジックを表現するためにMermaidを活用して可視化します。
Mermaidを利用するためにハックが必要なため、後述の「よくある課題と解決策」を参照してください。
テンプレート
get:
summary: [機能名を記述]
description: |
## 機能概要
- [機能の概要1]
- [機能の概要2]
- [機能の概要3]
<details>
<summary>処理詳細</summary>
## 処理フロー
1. [ステップ1]
2. [ステップ2]
3. [ステップ3]
## 処理フロー図
<pre class='mermaid'>
sequenceDiagram
participant Client
participant API
participant Cache
participant DB
Client->>API: [リクエスト]
API->>Cache: [処理1]
alt [条件1]
Cache-->>API: [結果A]
API-->>Client: [レスポンスA]
else [条件2]
API->>DB: [処理2]
DB-->>API: [結果B]
API-->>Client: [レスポンスB]
end
</pre>
## データモデル
<pre class='mermaid'>
erDiagram
[TABLE1] ||--o{ [TABLE2] : has
[TABLE2] }o--|| [TABLE3] : "related to"
[TABLE1] {
uuid id PK
string name
}
[TABLE2] {
uuid id PK
uuid table1_id FK
}
</pre>
## エラーケース
- [エラーシナリオ1]: [HTTPステータス]
</details>
記述例
get:
summary: コメント一覧取得
description: |
## 機能概要
特定の投稿に紐づくコメント一覧を取得します。
- 投稿の存在確認を行います。
- コメントデータを作成日時順に取得します。
<details>
<summary>処理詳細</summary>
## 処理フロー
1. パスパラメータ(postId)の検証
2. 投稿の存在確認(Redisキャッシュ活用)
3. キャッシュ確認(コメントデータ)
4. キャッシュなしの場合、DBクエリ実行
5. 作成者情報をJOINで取得(N+1問題回避)
6. キャッシュ保存(TTL: 3分)
7. レスポンス返却
## 処理フロー図
<pre class='mermaid'>
sequenceDiagram
participant Client
participant API
participant Cache
participant DB
Client->>API: GET /posts/{postId}/comments
API->>Cache: 投稿存在確認
alt 投稿が存在しない
Cache-->>API: 存在なし
API-->>Client: 404 Not Found
else 投稿が存在する
API->>Cache: コメントキャッシュ確認
alt キャッシュヒット
Cache-->>API: キャッシュデータ
API-->>Client: 200 OK
else キャッシュミス
API->>DB: コメント取得(JOIN)
DB-->>API: データ
API->>Cache: キャッシュ保存
API-->>Client: 200 OK
end
end
</pre>
## データモデル
<pre class='mermaid'>
erDiagram
POSTS ||--o{ COMMENTS : has
COMMENTS }o--|| USERS : "authored by"
POSTS {
uuid post_id PK
string title
text content
}
COMMENTS {
uuid comment_id PK
uuid post_id FK
uuid user_id FK
text content
}
</pre>
## N+1問題の回避
commentsテーブルとusersテーブルをINNER JOINで結合し、1回のクエリでコメント情報と作成者情報を同時に取得します。
```sql
SELECT
c.comment_id,
c.content,
u.username,
u.display_name,
c.created_at
FROM comments c
INNER JOIN users u ON c.user_id = u.user_id
WHERE c.post_id = $1
AND c.deleted_at IS NULL
ORDER BY c.created_at ASC
LIMIT $2 OFFSET $3;
```
## パフォーマンス要件
| シナリオ | 目標タイム |
|---------|----------|
| キャッシュヒット | 10ms以内 |
| 通常クエリ | 80ms以内 |
| 大量コメント | 150ms以内 |
</details>
よくある課題と解決策
1. ファイルサイズの肥大化
設計ドキュメントも記載するためOpenAPIファイルが大きくなり、エディタの動作が重くなる可能性があります。
$refを用いてファイルを分割してください。(公式)
# openapi.yaml(エントリーポイント)
paths:
/users:
$ref: './paths/users/index.yaml'
# paths/users/index.yaml
get:
summary: ユーザー一覧取得
description: |
[設計...]
2. descriptionが長くなり、request,responseまでスクロールが必要
設計の記述量が増えるとrequest,responseを参照するまでのスクロール量が増えます。
ツールで表示順を変更できれば良いですが簡単に変更する方法が見つからなかったため、<details>タグを用いて折りたたみ可能にします。
description: |
## 機能概要
特定の投稿に紐づくコメント一覧を取得します。
<details>
<summary>設計を見る</summary>
## 処理フロー図
<pre class='mermaid'>
sequenceDiagram
participant Client
participant API
...
</pre>
## データモデル
...
</details>
3. Mermaidが変換されない
標準のSwagger UIではMermaidが変換されず表示されません。
解決方法としてRedocly CLIを用いてカスタムテンプレートを使用します。
Mermaid.jsを組み込んだカスタムテンプレートで、HTML出力時にMermaidを図にレンダリングします。
このためmermaidを記載する際は <pre class='mermaid'></pre> で囲う必要があります。
完全なMarkdownで記載できない点はご了承ください。
# openapi.yaml(エントリーポイント)
paths:
/users:
get:
summary: ユーザー一覧取得
description: |
## 処理フロー図
<pre class='mermaid'>
sequenceDiagram
participant Client
participant API
...
</pre>
<!-- custom-template.hbs -->
<html>
<head>
<meta charset='utf8' />
<title>{{title}}</title>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<style>
body { padding: 0; margin: 0; }
/* デフォルトのスタイルでは背景色が黒で見づらいため変更 */
* pre.mermaid { background-color: #fafafa !important; }
</style>
{{{redocHead}}}
{{#unless disableGoogleFont}}
<link href='https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700' rel='stylesheet' />
{{/unless}}
</head>
<body>
{{{redocHTML}}}
<script type="module">
// Mermaidライブラリをインポート (ref: https://mermaid.js.org/intro/getting-started.html#requirements-for-the-mermaid-api)
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true, theme: 'forest' });
</script>
</body>
</html>
ビルドコマンド:
npx @redocly/cli build-docs openapi.yaml \
-o openapi.html \
-t custom-template.hbs
externalDocsは活用できるか
今回の集約する目的にはアンマッチです。
PathsにはexternalDocsというフィールドが存在します。
externalDocs – used to reference an external resource that contains additional documentation.
External Documentation Object
| Field Name | Type | Description |
|---|---|---|
| description | string | A description of the target documentation. CommonMark syntax MAY be used for rich text representation. |
| url | string | REQUIRED. The URI for the target documentation. This MUST be in the form of a URI. |
externalDocsはExternal Documentation Objectで定義されており、URLを対象としておりOpenAPIと別管理を想定しています。相対パスを指定することもできますがHTML出力時にリンクとして扱われるため単一資料として組み込むことができません。
実践ガイド
導入に向けて実際に手を動かしてみましょう。
以下の4ステップで今から始められます。
ステップ1: テンプレートをコピーする
上記のレベル1〜3から、あなたのプロジェクトに合ったテンプレートを選びます。
- プロジェクトに合ったテンプレートを選ぶ
- 既存のOpenAPIファイルの
descriptionフィールドに貼り付ける -
[...]の部分を自分のAPI情報で置き換える
例
# openapi.yaml
paths:
/users:
get:
summary: ユーザー一覧取得
description: |
## 機能概要
ページネーション対応のユーザー一覧を取得します。
## 処理フロー
1. パラメータのバリデーション
2. データベースクエリ実行
3. レスポンス整形
ステップ2: 1つのAPIで試す
実際に1つのAPIエンドポイントで設計を記述してみましょう。
まずはシンプルなGET APIから始めることをおすすめします。
- 機能概要を書く: 2〜3行でAPIの目的を記述
- 処理フローを書く: 主要な処理ステップを箇条書き
- エラーケースを書く: 想定されるエラーシナリオを列挙
最小限の例
get:
summary: ユーザー詳細取得
description: |
## 機能概要
指定されたユーザーIDのユーザー情報を取得します。
## 処理フロー
1. パスパラメータ(userId)のUUID検証
2. キャッシュ確認(Redis)
3. キャッシュミス時、DBからユーザー情報取得
4. 見つからない場合は404エラー
5. キャッシュ保存(TTL: 10分)
## エラーケース
- userIdがUUID形式でない場合: 400 Bad Request
- ユーザーが存在しない場合: 404 Not Found
ステップ3: HTML生成で確認
書いた設計が正しく表示されるか確認しましょう。
Mermaidを使わない場合
VSCode拡張機能やredoclyで簡単にプレビューできます。
方法1: VSCode拡張機能を使う
# VSCodeで「Swagger Viewer」拡張機能をインストール
# openapi.yamlを開いて、右上のプレビューボタンをクリック
方法2: redoclyでHTML生成
npx @redocly/cli build-docs openapi.yaml -o openapi.html
open openapi.html
Mermaidを使う場合
カスタムテンプレートを作成
<!-- custom-template.hbs -->
<html>
<head>
<meta charset='utf8' />
<title>{{title}}</title>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<style>
body { padding: 0; margin: 0; }
* pre.mermaid { background-color: #fafafa !important; }
</style>
{{{redocHead}}}
{{#unless disableGoogleFont}}
<link href='https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700' rel='stylesheet' />
{{/unless}}
</head>
<body>
{{{redocHTML}}}
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true, theme: 'forest' });
</script>
</body>
</html>
HTML生成
npx @redocly/cli build-docs openapi.yaml \
--template custom-template.hbs \
-o openapi.html
open openapi.html
ステップ4: チームで共有して標準化
チーム内で標準化しましょう。
- プルリクエストを作成: 1つのAPIで試した結果を共有
- フィードバックを収集: 記述レベルが適切か確認
- テンプレートを標準化: チーム標準テンプレートを作成
- レビュープロセスに組み込む: PRチェックリストに追加
チェックリスト例
## OpenAPI description レビューチェックリスト
- [ ] 機能概要が2〜3行で記載されているか
- [ ] 処理フローが3ステップ以上記載されているか
- [ ] 主要なエラーケースが列挙されているか
- [ ] SQLが記載されているか(データ取得系の場合)
標準化に関してはGitHub Actionsを用いた自動検証など工夫してください。
まとめ
今回はOpenAPIに設計を集約する方法を紹介しました。
最後にこのアプローチで得られる効果を整理します。
得られる効果
- OpenAPI定義書を唯一の真実の情報源に: API仕様と設計が1つのファイルに統合され、情報の不整合が起きにくくなります。
- HTML出力時も完全に表示: Swagger UIやredoclyでHTML生成しても情報が欠けず、リンク切れの心配がありません。
- バージョン管理との親和性: Gitで変更履歴が追跡しやすくなり、PRレビューで仕様と設計を同時確認できます。
- Mermaidも完全対応: redoclyのカスタムテンプレートを用いて図も美しく表示できます。