はじめに

変更APIの設計では、「全項目リクエスト」「変更項目のみリクエスト」のどちらを選ぶべきか、しばしば議論になります。どちらを選択するかで、APIの使いやすさや実装のシンプルさに大きく影響します。
この記事では、実際の経験に基づく具体的な問題とその解決策を示しながら、この永遠の?疑問に対する最適な選択を考えてみたいと思います。

初期の設計と仕様の選択

私のプロジェクトでは、以下の2つの選択肢がありました。

  • 選択肢 1: 変更された項目のみをリクエストボディに指定する
  例: {"product_name": "New Name"}
  • 選択肢 2: 全項目をリクエストする
  例: {"product_id": 1, "product_name": "New Name", "product_description": "Updated Description"}

明確な理由はなく経験だけでいくと、全項目リクエストの方が一般的だと考えていましたが、プロジェクトの要件やチームメンバーとの協議を通じて、最終的に変更項目のみをリクエストする仕様を選択することになりました。

実装段階で見えてきた問題点

実際の実装では、変更項目のみをリクエストする選択肢 1が以下のような問題を引き起こしました。特に、任意入力項目をNullに変更する場合の処理において、思わぬ困難が。。。

Pythonでの具体的な実装例
変更された項目のみをリクエストする場合、空文字列をNullとして扱う必要があります。以下のコードは、その処理の一例です。

def update_product(request_body):
    # 受け取ったリクエストボディ
    product_id = request_body.get("product_id")
    product_name = request_body.get("product_name")
    product_description = request_body.get("product_description")

    # データベースの更新処理
    update_fields = {}

    if product_name is not None:
        update_fields["name"] = product_name if product_name != "" else None

    if product_description is not None:
        update_fields["description"] = product_description if product_description != "" else None

    # SQLやORMを使ってデータベースを更新
    # 例: UPDATE products SET name = ?, description = ? WHERE id = ?
    update_database(product_id, update_fields)

このコードでは、Noneチェックと空文字列の変換処理が必要です。これにより、以下のような課題が生まれます。

  • 空文字列をNullとして扱いたい場合
    リクエストに{“product_name”: “”}を送信してデータベースをNullに更新するには、追加の変換処理が必要です。
  • 未指定項目の区別
    リクエストボディに”product_name”:nullと指定すると、項目が未指定なのか、Nullにしたいのかの区別がつかず、更新が正しく行われない可能性があります。

変更された項目のみをリクエストする設計では、リクエストボディに項目が存在しない場合と、存在しているけど値がNoneまたは空文字列の場合を区別する必要があります。この区別が、仕様設計や実装における課題となりました。

問題の解決策
全項目をリクエストする仕様にした場合、実装は次のようにシンプルになります。

def update_product(request_body):
    # 受け取ったリクエストボディ
    product_id = request_body.get("product_id")
    product_name = request_body.get("product_name", None)  # キーが存在しない場合にNoneを設定
    product_description = request_body.get("product_description", None)

    # 空文字列をNoneに変換してデータベースのNullに対応
    update_fields = {
        "name": product_name or None,  # 空文字列もNoneに変換
        "description": product_description or None
    }

    # SQLやORMを使ってデータベースを更新
    update_database(product_id, update_fields)

この場合、リクエストボディにnullまたは未指定の項目がある場合でも、シンプルにデータベースをNullに更新できます。変換処理が不要になり、実装がより直感的で分かりやすくなります。

コードの分かりにくさは、メンテナンスなどを経て思わぬバグを生む。。できれば避けたいです。

設計段階での見落とし

設計段階ではこのような具体的な問題が見えていなかったため、変更項目のみのリクエスト仕様にしてしまいました。設計時にこれらの問題を予測できていれば、全項目リクエストの方がより適していると判断できたかもしれません。

API呼び出し元の視点からの考察

API呼び出し元の使いやすさを考えてみると、一般的な利用パターンでは次の流れが想定されます。

  1. 検索APIで全項目を取得
    例: GET /products/1
    レスポンス: {“product_id”: 1, “product_name”: “Name”, “product_description”: “Old Description”}
  2. 変更された項目を更新APIで送信
    全項目リクエストでも、変更項目のみリクエストでも、最初に取得したデータを基に更新するため、大差はありません。

実際には、変更した項目を特定してリクエストするよりも、全項目を指定する方が簡単で便利です。以下のように全項目リクエストを行う方が直感的です。

{
    "product_id": 1,
    "product_name": "Name",
    "product_description": "New Description"
}

結論: 選択のポイント

これらの経験を踏まえ、特別な理由や要件がない限り、変更APIは基本的に全項目リクエストを採用するほうが良いと考えます。以下にメリットをまとめます。

  • 実装の簡素化: 複雑なロジックや変換処理が不要になり、コードがシンプルになります。
  • 呼び出し元の利便性: 呼び出し元での変更項目の特定が不要で、より簡便にAPIを利用できます。
  • 柔軟性の向上: 全項目リクエストにより、将来的な拡張や仕様変更に対応しやすくなります。

ただし、項目数が非常に多い場合や、特定のパフォーマンス要件がある場合、またはビジネスロジック上の要件がある場合には、変更項目のみリクエストが適している場合もあります。そのような場合は、プロジェクトの要件を慎重に評価し、適切な選択を行うことが重要です!

さいごに

あまり深く考えずこれまでのやり方を踏襲するケースは多いけれど、今後は一度立ち止まって「これでいいのか?」と考える時間を作るようにしようと思います!