AI開発

APIの冪等性とは|二重登録を防ぐ5つの仕様

株式会社Atsumell|9分で読めます
APIの冪等性と二重登録を防ぐ5つの仕様を示すサムネイル

はじめに

受注登録APIを呼び出したが、応答が返る前にタイムアウトした。画面には「失敗」と出ている。もう一度登録ボタンを押すと、注文が二件できてしまった。

これは説明用の例だが、設計で向き合うべき問いは明確だ。返事がないことと、処理されていないことは同じではない。人が操作する画面でも、AIエージェントが外部ツールを使う場面でも、結果不明の処理を新しい要求として送り直す前に、何を確認するかを決めておきたい。

APIの冪等性は、同じ操作を繰り返しても追加の副作用を起こさないための性質である。本記事では、登録系APIの再試行に絞り、業務担当者と開発会社が合意する5つの仕様を示す。コードの書き方ではなく、どの要求を同じ操作とみなし、いつ再送を止めるかが中心だ。

冪等性は「一度しか通信しない」ことではない

Amazon Builders’ Libraryは、再試行しても追加の副作用が生じない操作を冪等な操作として説明する。通信自体は複数回でも、注文やリソースの作成が増えない契約を考える。

ただし、冪等性キーを付ければ、業務全体が自動的に一度だけ実行されるわけではない。受注APIが重複登録を防いでも、その後の通知や別システムへの転記は別の境界である。それぞれについて、何を守るかを確認する必要がある。

また、同じ入力だから同じ操作とも限らない。顧客が同じ商品を二回注文することは正当な業務だ。AWSの解説でも、同一パラメータから重複を推定する方法には限界があり、呼出側が与える要求識別子で意図を表す方式が紹介されている。

まずは「注文の確定一回」を一つの論理操作として扱う。その論理操作に付けたキーを再試行でも引き継ぐ。再試行ごとにキーを作り直す設計では、受信側から見ると別の注文になってしまう。

二重登録を防ぐ5つの仕様

以下は、公式資料の考え方を踏まえた設計提案である。数字や状態名は例であり、利用するAPIが保証する契約に合わせて調整する。

1. キーを発行する単位と担当を決める

キーの単位は、HTTP通信一回ではなく、業務上の操作一回にする。注文確定、請求書作成、顧客登録など、目的を動詞で書く。

キーの発行者と保存先も決める。画面が発行するなら、再読込後も同じ未完了操作を再開できるかを確認する。サーバーが発行するなら、受付記録と紐づけて保存する。AIエージェントの場合も、文章から毎回キーを推測させず、実行基盤が保持する操作記録に対応させる設計が考えられる。

Stripeの公式APIリファレンスでは、クライアントがキーを生成し、同じ要求の再試行を認識する仕組みを説明している。また、キーにメールアドレスなどの機微な情報を使わないよう案内している。ログへ出る可能性のあるキーには、顧客情報そのものを埋め込まない方針を置きたい。

自社APIでは、キーを単独で全顧客共通の識別子にするのか、テナントや操作種別と組み合わせるのかも明記する。重複判定より前に呼出主体を確認し、別の顧客の処理結果を返さないことを受け入れ条件にする。

2. 同じキーに異なる入力が来た場合を決める

「同じキーなら前回の結果を返す」だけでは、入力を変更した再送の扱いが曖昧になる。たとえば初回の注文数量は一個、再送は二個だった場合、どちらを採用するのか。

Stripeは、同じキーの要求について入力パラメータを初回と比較し、異なる場合はエラーにすると説明している。自社APIでも、同じキーで異なる業務内容を受け付けない契約を候補にできる。

照合対象は項目一覧で決める。注文先、明細、数量、通貨などの業務項目と、追跡用の時刻やリクエストIDを区別する。JSONの文字列順序が違うだけで拒否するのか、意味の同じ入力として正規化するのかも実装担当と合意する。

変更した内容を本当に新しい操作として実行する場合は、初回が未適用か、取り消されたか、別注文として扱ってよいかを確認する。「エラーを回避するため新しいキーへ替える」は、再試行の手順に含めない。

3. 同時要求と途中停止を扱う

二つの要求が同時に到着したとき、両方が「未処理」と判断して登録を始めないかを確認する。単純な「検索して、なければ追加」という説明だけでは、この競合を防ぐ条件が見えない。

AWSの解説は、要求識別子の記録と対象の変更を原子的に扱う重要性を示している。自社内の登録なら、一意制約やトランザクションなど、実装が守る境界を具体化する。外部サービスへの送信まで単一のデータベース処理で完結すると決めつけてはいけない。

設計例として、状態を「受付」「処理中」「成功」「未適用の拒否」「結果不明」に分ける。同じキーの処理中要求には待機を案内するのか、状態照会先を返すのかを決める。処理中の記録が残ったまま実行基盤が停止した場合には、誰がどの証拠で再開を判断するかも書く。

完了していない状態を成功として返すことと、結果不明を未実行として扱うことを、どちらも禁止条件にする。

4. キーと結果の保存期限を決める

重複防止の記録は、いつまで再試行を保護するのか。ここが曖昧なままでは、翌日の再送と一週間後の手動復旧を同じ手順で扱ってしまう。

Stripeの公式資料では、少なくとも24時間経過したキーを削除でき、削除後に同じキーが使われると新しい要求になると説明している。これはStripeの契約であり、すべてのAPIに共通する保存期間ではない。

自社APIの期限は、通信の自動再試行だけでなく、キューの滞留、夜間バッチ、人による翌営業日の復旧も見て決める。期限を過ぎた結果不明の操作は、自動再送せず、注文番号などの業務記録を照合する手順へ切り替える案が考えられる。

なお、再試行用の結果キャッシュと、業務上の注文履歴は目的が異なる。前者の期限が切れても、後者から処理結果を確認できる設計かを見たい。削除条件、照合可能な期間、運用担当の確認権限を別々に書く。

5. 成功・拒否・結果不明の確認方法を決める

応答コードだけで成功を判断するのか、登録された注文IDの取得まで必要かを決める。非同期受付なら、受付の成功と業務処理の完了を分ける。

Stripeは、処理開始後に保存した最初のステータスコードと本文を、同じキーの再要求へ返し、500エラーもその対象になると説明している。一方で、入力検証の失敗など実行開始前のケースでは結果を保存しないとしている。つまり、「同じキーで再送すれば必ず成功へ進む」とは言えない。

自社仕様にも、提供APIの再試行条件、状態照会API、操作キーと対象IDの対応を載せる。状態照会に失敗したことを「対象がない」と読み替えない。対象なしが確認できた場合も、その確認方法が未適用の証明になるかは契約次第だ。

HTTPエラーの設計と重複防止は別々の資料に散らばりやすい。全体の契約はAPI仕様書の書き方へ、連携の担当と方向は外部インターフェース一覧へ紐づけて管理したい。

受注登録APIへ落とす記入例

以下は自社APIの仕様検討用の例であり、特定サービスの保証ではない。

項目合意する内容の例
論理操作利用者が承認した一つの注文確定
キー管理実行基盤が発行し、受付記録へ保存。再試行時は維持
判定範囲テナントID・操作種別・キーの組み合わせ
入力照合注文先・明細・数量・通貨を照合。差分は拒否
処理中二重実行せず、認可された状態照会先を案内
成功注文IDと登録結果を保存。同じ操作には同じ注文IDを返す
結果不明外部登録結果を照合。判定できなければ運用担当へ移管
期限後無条件に再送せず、業務記録を照合して対応を判断

この表の価値は、方式名を選ぶことより、未決事項を見えるようにすることにある。保存期限や照合権限が未定なら、担当者と決定期限を置く。仕様書の空欄を、開発者やAIの推測で埋めない。

受け入れテストでは応答を意図的に失わせる

正常な要求を一回送るテストだけでは、冪等性の契約を確認できない。まず、登録は完了したがクライアントへ応答が届かない状況を試験環境で作る。同じキーで再送し、注文が一件のままか、初回の注文IDへ辿れるかを確認する。

加えて、次のケースを検収項目にする。

  • 同じキー・同じ入力を同時に送り、登録件数が増えない
  • 同じキー・異なる数量を送り、黙って上書きされない
  • 別テナントから同じキーを送り、他社の結果が返らない
  • 処理途中で停止させ、未完了を成功扱いしない
  • 保存期限の境界を越え、仕様どおり照合へ切り替わる
  • 外部照会が失敗し、対象なしではなく未確認として残る

期待結果には応答だけでなく、業務データの件数、状態、対象ID、操作ログを含める。登録は一件でも通知が二回出たなら、通知を含む業務契約としては改善が必要だ。どこまでを今回の検収範囲に含めるかを明記しておこう。

まとめ:再送より先に、同じ操作を追跡できるようにする

APIの冪等性は、キーを一つ追加して終わる機能ではない。発行単位、入力照合、同時実行、保存期限、結果確認の5点を揃えて初めて、結果不明の操作へ落ち着いて対応できる。

API設計書を見直すなら、タイムアウト後に誰が何を見るかを一つの業務フローへ書き込んでみよう。「再実行する」の一行が、照合、待機、拒否、人への移管へ分かれるはずだ。

業務フローと仕様を同じ文脈で整理したい方は、Kakusillの紹介も参考にしてほしい。API連携の例外処理や受け入れ条件を具体化したい場合は、Atsumellへのお問い合わせからご相談ください。

#API設計#冪等性#システム連携#要件定義#AIエージェント