🔍

アジャイル開発でAPI処理詳細はどこまで書くべきか?──現場が陥る「書きすぎ」と「書かなすぎ」の境界線

に公開

アジャイル開発で「API の処理詳細はどこまで設計書に書くべきか」という議論は、チームの生産性を静かに蝕む。書きすぎれば誰も更新しなくなり、書かなすぎれば新メンバーが立ち上がるのに 3 週間かかる。この記事は、その両方で痛い目に遭った経験から導き出した、現場で機能する判断基準を共有する。

「処理詳細設計書、書きますか?」──現場で必ず起きる議論

あなたのチームでこんな会話が交わされたことはないだろうか。

PL: 「この API、DB のどのテーブルのどのカラムを使うか、処理ロジックの分岐条件、全部設計書に落としてほしい」
開発者: 「え、アジャイルなのに詳細設計書書くんですか? コード見ればわかりますよね?」
PL: 「後から入るメンバーがキャッチアップできないだろう」
開発者: 「でも仕様変わるたびにメンテするの、誰がやるんですか?」

この議論が不毛なのは、「書くか書かないか」の二項対立になっているからだ。本当に問うべきは「何を、どの粒度で、どこに書くか」である。

筆者はウォーターフォールで詳細設計書を山ほど書いた時代と、アジャイルで「ドキュメント不要」を掲げて痛い目に遭った時代の両方を経験してきた。

後者の失敗を 1 つ挙げる。新規プロダクト開発で「コードが仕様書だ」を徹底した結果、半年後に入社したメンバーが「どの API がどのビジネスルールを実装しているか」を把握するのに 3 週間かかった。テストは書いていたが、テスト名が英語で機能名ベースだったため、ビジネスルールとの対応が読み取れなかったのだ。


アジャイル宣言の「ドキュメント」を正しく読む

アジャイルソフトウェア開発宣言の原文はこうだ。

包括的なドキュメントよりも動くソフトウェアを
(Working software over comprehensive documentation)

「包括的な(comprehensive)」が重要だ。すべてを網羅する設計書は要らないが、必要なドキュメントまで捨てろとは言っていない。アジャイル開発の提唱者の一人、Scott Ambler も「必要最小限のドキュメントを、必要なタイミングで(just enough, just in time)」と繰り返し述べている。

つまり、問題は「ドキュメントの量」ではなく「ドキュメントの ROI(書くコストに対して、チームが得られる価値)」だ。


処理詳細の「何を」「どこに」書くかを分解する

API 開発における処理詳細を分解すると、以下の要素がある。この表の「推奨管理場所」は、次のセクションで解説する「4 つの問い」を先取りしてまとめたものだ。

要素 具体例 変更頻度 推奨管理場所
エンドポイント定義 POST /api/v1/orders OpenAPI 定義
リクエスト/レスポンス構造 パラメータ名、型、必須/任意 OpenAPI 定義
DB 項目マッピング orders.total_amount ← リクエストの amount テストコード
バリデーションルール amount > 0, status in [pending, confirmed] OpenAPI スキーマ
ビジネスロジック 「在庫が 0 なら 409 を返す」「税率は注文日時点のものを適用」 テストコード
エラーハンドリング 404 / 409 / 422 の使い分け OpenAPI 定義
認証・認可 「管理者ロールのみ削除可能」 OpenAPI 定義

ここで気づくべきことがある。変更頻度が高いものほど、Word/Excel の設計書に書くとメンテナンスコストが爆発する。ただし、変更頻度だけで判断するのは不十分だ。次のセクションで判断基準を整理する。


判断基準:4 つの問いで決める

処理詳細をどこに書くか迷ったら、以下の 4 つの問いを順番に通す。

問い Yes の場合 No の場合
1. インターフェースか? 明文化する(OpenAPI) 次の問いへ
2. 他者の意思決定を助けるか? 書く価値あり コード/テストで十分
3. 自動生成できるか? 手で書かない 次の問いへ
4. 更新担当者が決まっているか? 書いて管理 書かない

問い 1:「それはインターフェースか、実装か?」

インターフェース(エンドポイント、リクエスト/レスポンス形式、エラーコード)は契約だ。ここで言う「契約」とは、フロントエンドや他チーム、外部パートナーが「この仕様に従ってリクエストを送れば、この形式でレスポンスが返る」と信頼して依存する取り決めのことだ。勝手に変えたら相手が壊れる。だから明文化する価値が高い。

実装(内部の DB 項目マッピング、ロジック分岐の詳細)はチーム内の関心事だ。コードとテストで表現するほうがメンテナンスコストが低い。

問い 2:「書いた人以外が読んで、意思決定に使うか?」

  • フロントエンドチームが API 仕様を見てリクエストの組み立て方を決める → 書くべき
  • 同じバックエンドチームの開発者が DB カラムの使い方を知りたい → コードとテストで十分
  • 半年後に入る新メンバーがシステムの全体像をつかみたい → ER 図とシステム構成図で対応

問い 3:「自動生成できないか?」

手書きのドキュメントは腐る。コードから自動生成できるドキュメントは、手で書かない

ドキュメント 自動生成の手段 補足
API 仕様書 OpenAPI / Swagger コードに annotation を書いて YAML を生成する方法(コードファースト)と、YAML を先に書いてコードを生成する方法(スキーマファースト)がある
DB スキーマ マイグレーションファイル + ER 図自動生成ツール SchemaSpy や tbls は DB 接続から ER 図を自動生成するツール
リクエスト例 テストコードから生成 Prism は OpenAPI 定義からモックサーバーを立てるツール。Dredd は定義とコードの整合性を検証するツール

チームが OpenAPI 初心者なら、まずコードファーストで始めるのが導入のハードルが低い。Python なら FastAPI(自動で OpenAPI を生成する)、Node.js なら swagger-jsdoc が導入しやすい。フロントエンドとバックエンドの並行開発が必要になったタイミングで、スキーマファーストへ移行するのが現場での定番パターンだ。

問い 4:「変更されたとき、誰がドキュメントを更新するか?」

この問いに明確な答えがないなら、そのドキュメントは腐る運命にある。「コードを変えたら自動で反映される」仕組みにできないなら、書かない方がマシだ。


やらない判断:こういうドキュメントは書かない

判断基準が分かったところで、まず「書かない方がいいもの」を先に片付ける。

1. Excel の処理詳細設計書(項目定義書)

問題の本質:コードと二重管理になり、片方が必ず腐る

【やりがちな例】
No. | 処理名    | 入力          | 出力           | 処理内容
1   | 在庫確認  | product_id    | Boolean        | productsテーブルのstock_quantityが
    |           | quantity      |                | quantity以上であることを確認する
2   | 注文作成  | product_id    | order_id       | ordersテーブルにINSERTする
    |           | quantity      |                | statusはpendingとする
    |           | user_id       |                |

書かない理由:

  • スプリントごとに仕様が変わるたびに更新が必要で、更新漏れが頻発する
  • 同じ情報がコード、テスト、この Excel の 3 箇所に散在する(Single Source of Truth──「唯一の正となる情報源」の違反)
  • 書く時間で、テストを 1 本多く書くほうがチームの生産性が上がる

2. DB 全カラムの CRUD マッピング表

問題の本質:「○」では条件が読めず、結局コードを見ることになる

【やりがちな例】
テーブル名 | カラム名       | C | R | U | D | 備考
orders     | id             |   | ○ |   |   | 自動採番
orders     | user_id        | ○ | ○ |   |   | ログインユーザーIDを設定
orders     | status         | ○ | ○ | ○ |   | 初期値:pending
orders     | total_amount   | ○ | ○ |   |   | 明細の合計

書かない理由:

  • マイグレーションファイルと ORM(Django の models.py、Rails の ActiveRecord 等)の定義を見れば全カラムの情報は取れる
  • 「○」だけでは「どういう条件で UPDATE されるのか」がわからず、結局コードを読むことになる
  • カラム追加のたびにこの表を更新する運用が定着した現場を見たことがない

3. 処理フローを自然言語で書いた長文仕様書

問題の本質:自然言語は曖昧で、テストコードのほうが正確

【やりがちな例】
「注文作成処理では、まずリクエストパラメータの product_id を用いて
products テーブルを検索し、該当レコードの stock_quantity フィールドが
リクエストパラメータの quantity 以上であることを確認する。
条件を満たさない場合は 409 ステータスコードを返却する。
条件を満たす場合は orders テーブルに新規レコードを INSERT し......」

書かない理由:

  • この文章を正確に書くのに 30 分かかるなら、その 30 分でテストコードを書くべき
  • 自然言語は曖昧さを含みやすい(「以上」は >>= か?)
  • 500 行の処理仕様書より、50 行のテストコードのほうが正確で保守可能

現場で機能する「3 層ドキュメント戦略」

「書かないもの」を明確にしたところで、「では何を書くか」を整理する。API 開発の処理詳細は以下の 3 層に分けて管理するのが効果的だ。

第 1 層:契約(必ず書く)

OpenAPI 定義ファイルとして管理する。

# openapi.yaml
paths:
  /api/v1/orders:
    post:
      summary: 注文を作成する
      # description は「このAPIが何をするか」と「いつ4xxを返すか」に限定する。
      # 処理の順序や内部のサブステップは書かない。それはテストの責務だ。
      description: |
        在庫チェック後に注文を確定する。
        在庫不足の場合は 409 Conflict を返す。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              # $ref は他の場所で定義したスキーマを参照する記法
              $ref: '#/components/schemas/CreateOrderRequest'
      responses:
        '201':
          description: 注文作成成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '409':
          description: 在庫不足
        '422':
          description: バリデーションエラー

components:
  schemas:
    CreateOrderRequest:
      type: object
      required:
        - product_id
        - quantity
      properties:
        product_id:
          type: string
          format: uuid
          description: 商品ID
        quantity:
          type: integer
          minimum: 1
          description: 注文数量(1以上)

ポイント:

  • description にビジネス上の意味を書く(「在庫チェック後に注文を確定する」)
  • バリデーションルールはスキーマの minimum, pattern, enum で表現する
  • エラーレスポンスの いつ返すかdescription に書く

これは Swagger UI で自動的にドキュメント化されるし、コードとの乖離はリンターやテストで検出できる。

第 2 層:構造(チームで共有)

ER 図システム構成図を Mermaid や PlantUML で管理する。

ポイント:

  • テーブル定義の「なぜその設計か」をコメントで残す(例:"注文時点の単価を保存"
  • これは Git で管理し、マイグレーションと同じ PR で更新する

ER 図は「全カラムの型定義」ではなく「テーブル間の関係性とビジネス上の意味」を伝えるために書く。カラムの正確な型定義はマイグレーションファイルが唯一の正(Single Source of Truth)だ。

第 3 層:ロジック(コードとテストで表現)

DB のどのカラムをどう使うか、分岐条件の詳細はテストコードで表現する。以下は Django + pytest の例だが、他のフレームワーク(Express + Jest、Spring Boot + JUnit 等)でも同じ考え方で書ける。

# test_create_order.py(Django + pytest の例)

class TestCreateOrder:
    """注文作成APIの処理詳細"""

    def test_正常に注文が作成される(self):
        """
        処理フロー:
        1. product_id で商品を取得
        2. stock_quantity >= quantity を検証
        3. orders レコードを作成(status='pending')
        4. order_items レコードを作成(unit_price は products.current_price をコピー)
        5. products.stock_quantity を quantity 分だけ減算
        """
        product = create_product(stock_quantity=10, current_price=1500)

        response = client.post("/api/v1/orders", json={
            "product_id": str(product.id),
            "quantity": 3,
        })

        assert response.status_code == 201
        order = response.json()
        assert order["status"] == "pending"
        assert order["total_amount"] == 4500  # 1500 * 3

        # DB から最新状態を再取得して、在庫が減っていることを検証
        # ※ refresh_from_db() を呼ばないと Python オブジェクト側の
        #    古い値を見てしまい、DB への書き込みを検証できない
        product.refresh_from_db()
        assert product.stock_quantity == 7  # 10 - 3

    def test_在庫不足で409エラー(self):
        """在庫数 < 注文数の場合、注文は作成されない"""
        product = create_product(stock_quantity=2, current_price=1500)

        response = client.post("/api/v1/orders", json={
            "product_id": str(product.id),
            "quantity": 5,
        })

        assert response.status_code == 409

        # DB から最新状態を再取得して、在庫が変わっていないことを検証
        product.refresh_from_db()
        assert product.stock_quantity == 2

    def test_注文時点の単価が保存される(self):
        """
        order_items.unit_price には注文時点の products.current_price を保存する。
        後から商品の価格が変わっても、注文時の価格が保持される。
        """
        product = create_product(current_price=1500)

        response = client.post("/api/v1/orders", json={
            "product_id": str(product.id),
            "quantity": 1,
        })
        order_id = response.json()["id"]

        # 商品価格を変更
        product.current_price = 2000
        product.save()

        # 注文の単価は変わらない(注文IDで特定して検証)
        order_item = OrderItem.objects.get(order__id=order_id)
        assert order_item.unit_price == 1500  # 注文時点の価格

なぜテストが「設計書」として機能するのか:

  1. 実行可能:CI で毎回検証されるため、コードとの乖離が物理的に起きない
  2. 具体的:「在庫チェック」という曖昧な言葉ではなく stock_quantity >= quantity という具体的な条件が示される
  3. ビジネスルールが読める:テスト名と docstring が「なぜこの処理が必要か」を説明している
  4. 変更検知:ロジックが変わったらテストが落ちるので、「設計書の更新忘れ」が起きない

ただし、テストが設計書として機能するのはテスト駆動開発(TDD)でテストを先に書いた場合が最も効果的だ。既存コードに後からテストを追加した場合、実装に引きずられて「正しく動くことの確認」止まりになりやすく、ビジネスルールが読み取れないテストになることがある。


例外:こういう場合はドキュメントを書く

3 層戦略は「チーム内のバックエンド開発」を念頭に置いている。以下のケースでは、その前提が崩れるため、追加のドキュメントを書く価値がある

ケース 1:複数チーム・複数システムを跨ぐ連携

自チームの API を別チームが呼ぶ場合、OpenAPI 定義だけでは伝わらないビジネスコンテキストがある。

## 注文 API 連携仕様(決済チーム向け)

### 呼び出しタイミング
- ユーザーが「注文確定」ボタンを押した後
- 決済処理が成功した後に POST /api/v1/orders を呼ぶ

### 冪等性(同じリクエストを何度送っても結果が変わらない性質)
- 同一の idempotency_key で複数回呼ばれた場合、2回目以降は既存の注文を返す
- 決済のリトライ時にも安全に呼び出せる

### タイムアウト・リトライ方針
- タイムアウト: 5秒
- リトライ: 最大3回(exponential backoff)
- 5xx エラー時のみリトライ、4xx はリトライしない

これは OpenAPI の description だけでは表現しきれない「運用上の合意事項」であり、ドキュメントとして残す価値がある。

ケース 2:規制・コンプライアンス要件

金融系や医療系など、「なぜこの処理がこうなっているか」を監査対応で説明する必要がある場合。

## 税額計算ロジック(監査対応用)

### 根拠法令
- 消費税法 第28条(課税標準)
- 国税庁タックスアンサー No.6383

### 計算方式
- 税抜価格 × 税率(端数切り捨て)で品目ごとに計算
- 合計後の再計算は行わない(品目別計算方式)

### 実装箇所
- `src/domain/tax/calculator.py:TaxCalculator.calculate()`
- テスト: `tests/domain/tax/test_calculator.py`

ケース 3:チーム内のスキルギャップが大きい場合

経験 10 年のシニアが設計し、経験 1 年のジュニアが実装する場合、口頭の説明だけでは認識がずれる。ただし、書く先は Jira チケットやプルリクエストの descriptionであり、独立した設計書ファイルではない。

<!-- Jira チケット or PR の description -->

## 実装方針

### やること
- `POST /api/v1/orders` エンドポイントを追加
- orders テーブルと order_items テーブルにレコードを作成
- 在庫の減算はトランザクション内で行う

### 判断ポイント
- unit_price は注文時点の価格をコピーする(後から商品価格が変わっても注文価格は変わらない)
- stock_quantity の減算は SELECT FOR UPDATE で排他制御する
  (同時に注文が来た場合、先にロックを取った方が処理を進め、もう一方は待機する仕組み)

### 参考
- ER図: docs/er-diagram.md
- 既存の類似実装: `src/api/v1/products.py`

ただし、Jira チケットの説明は「そのスプリント」でしか読まれないことが多い。半年後の新メンバーもアクセスできるよう、重要な設計判断はリポジトリの docs/decisions/ 以下に ADR(Architecture Decision Record)として残しておくと、チームの知識が属人化しにくい。Jira チケットに書いた設計判断は、チケットクローズ前に ADR へ転記するルールを設けるのが望ましい。


現場で遭遇する罠と対策

罠 1:「OpenAPI 定義はあるけど誰も読まない」

原因: Swagger UI が開発環境にしかデプロイされていない、または URL が周知されていない。

対策:

  • PR レビュー時に「OpenAPI 定義は更新したか?」をチェックリストに入れる
  • CI で OpenAPI 定義とコードの整合性を自動チェックする(spectral 等のリンターツール)
  • フロントエンドチームのオンボーディングに「Swagger UI の URL」を含める
  • 根本的な解決策: Prism で OpenAPI 定義からモックサーバーを立て、フロントエンドが実際に使える環境を整える。「読まれる」ためには「使う動機」が必要だ

罠 2:「テストが設計書の代わりなら、テストを読めばいいんでしょ?」と言われて破綻

原因: テストの命名や構造が雑で、ビジネスルールが読み取れない。

対策:

  • テスト名は日本語で、ビジネスルールを記述する(test_在庫不足で409エラー
  • テストファイルの docstring に処理フローの概要を書く
  • テストを「正常系」「異常系」「境界値」に分類して整理する

罠 3:「ER 図を書いたけど、3 スプリントで古くなった」

原因: ER 図が PowerPoint で管理され、更新のハードルが高い。

対策:

  • Mermaid や PlantUML でテキストベースにし、Git で管理する
  • マイグレーション作成時に ER 図も同じ PR で更新するルールを設ける
  • 完璧な ER 図より「テーブル間の関係が分かる程度」で十分

罠 4:「アジャイルだからドキュメント不要」と言い張るメンバー

原因: アジャイル宣言の誤読。「ドキュメントに価値がない」と解釈している。

対策:

  • 新メンバーのオンボーディング後、「詰まった箇所を Slack に投稿してもらう」ルールを設ける
  • 投稿された内容をドキュメントの Issue に積み上げ、改善に使う
  • 「実際に困った人がいる」という事実は、ドキュメント不要論者への最も有効な反論になる

限界と向き合う

この記事で提案した「3 層ドキュメント戦略」にも限界がある。正直に書いておく。

テストが設計書として機能するには前提がある。 テストカバレッジが低いプロジェクト、テストの品質が低いプロジェクトでは、テストを「設計書代わり」にはできない。テスト文化がないチームにいきなり「テストが設計書です」と言っても機能しない。まずテストを書く文化を育てるのが先だ。

「コードが設計書」は、コードの品質が高い場合にのみ成立する。 命名が雑、関数が 300 行、責務が不明確なコードは、読んでも処理詳細がわからない。リーダブルコードの実践が前提条件になる。

並行制御はテストで表現しにくい。 SELECT FOR UPDATE のような排他制御は、並行テストを書かないと検証できない。スレッドやプロセスを使った並行テストはセットアップコストが高く、多くのチームでは実装されていない。このような設計上の判断は PR の description に明示し、コードレビューで確認するしかないケースがある。

OpenAPI ファーストは初期コストがある。 スキーマ定義を先に書く習慣がないチームでは、最初の 2〜3 スプリントは「面倒くさい」という抵抗が出る。これは経験上ほぼ例外がない。対策は小さく始めることだ。まず 1 つの API だけ OpenAPI 定義を先に書く、というルールから始めると定着しやすい。

受託開発では顧客が詳細設計書を要求するケースがある。 契約上「設計書の納品」が含まれている場合、この記事の方針をそのまま適用はできない。長期契約であれば「OpenAPI 定義を合意フォーマットとする」契約変更を顧客と交渉する価値がある。ただし、顧客指定の Excel フォーマットが固定されている場合、自動生成は現実的でないことも多い。その場合は「設計書は OpenAPI 定義を印刷したものを正とする」といった合意を初期段階で取ることが、後のメンテナンスコストを最小化する、現場でとれる最善手だ。


まとめ:明日からできるアクション

Step 1:チームで「3 つの層」を合意する

何を書くか どこに書くか 更新タイミング
契約 API のインターフェース仕様 OpenAPI 定義ファイル API 変更時(CI で整合性チェック)
構造 テーブル関連と全体像 Mermaid/PlantUML(Git 管理) マイグレーション作成時
ロジック 処理の分岐・DB 操作の詳細 テストコード + PR description コード変更時(自動)

Step 2:既存の「処理詳細設計書」を仕分ける

  1. 既存の設計書を 1 つずつ開く
  2. 「この情報は OpenAPI / ER 図 / テストのどれに含まれているか?」を確認する
  3. 含まれていれば設計書は archive(削除ではなくアーカイブ)
  4. どこにも含まれていなければ、適切な層に移行する

経験上、1 チームが過去の設計書を仕分けるのに 1 スプリント程度かかる。最初から完璧を目指さず、「次に触る API から順に移行する」のが挫折しないコツだ。

Step 3:「書かない」ルールを明文化する

チームの Working Agreement(チーム内の作業ルールを文書化したもの)に以下を追加する。

## ドキュメントルール
- 処理ロジックの詳細は Excel/Word に書かず、テストコードで表現する
- API 仕様は OpenAPI 定義を唯一の正(Single Source of Truth)とする
- ER 図はテキストベースで Git 管理し、マイグレーションと同時に更新する
- 上記で表現できない情報(運用合意、監査要件等)のみ別途ドキュメント化する

参考資料

GitHubで編集を提案

Discussion