【ITニュース解説】Designing an Idempotent Payment API in ASP.NET Core
2026年09月28日に「Dev.to」が公開したITニュース「Designing an Idempotent Payment API in ASP.NET Core」について初心者にもわかりやすく解説しています。
ITニュース概要
支払いAPIなどでネットワーク不調によるリトライで重複課金が起きる問題を、べき等性キーパターンで解決する。リクエストに一意のキーを付与し、サーバーが初回処理結果を保存。再リクエスト時には保存済み結果を返すことで、重複処理を防ぎAPIの堅牢性を高める。
ITニュース解説
システムエンジニアを目指す初心者が、API開発の現場で直面する可能性のある重要な設計思想の一つに「べき等性(Idempotency)」がある。特に、決済処理のようにユーザーの金銭に関わる操作を扱うAPIを開発する際には、このべき等性の確保が極めて重要となる。今回のニュース記事は、ASP.NET CoreというWebアプリケーションフレームワークを使って、このべき等性をどのように実現するかについて、具体的なパターンと実装例を解説している。
まず、なぜべき等性が必要なのかを理解する必要がある。Webサービスの世界では、クライアント(例えばスマートフォンのアプリやWebブラウザ)とサーバー(APIを提供しているシステム)の間で常に安定した通信が行われるとは限らない。ネットワークの遅延や一時的な切断、サーバー側の一時的な高負荷など、さまざまな理由で通信が失敗したり、タイムアウトしたりすることがある。このような場合、クライアントは「リトライ」、つまりもう一度同じリクエストをサーバーに送ることが一般的だ。
ここで問題が発生する。もしAPIが「状態を変更する操作」、例えば決済処理やデータの更新を行うものだった場合、クライアントがリトライすると、サーバーは同じ操作を複数回実行してしまう可能性がある。今回の記事の例で言えば、ユーザーが決済ボタンを押したにもかかわらず、ネットワークの問題でレスポンスが返ってこなかった場合、ユーザーはもう一度ボタンを押すかもしれない。このとき、もしAPIがべき等でなければ、最初のリクエストが実はサーバー側で正常に処理されていたとしても、2回目のリクエストも処理されてしまい、結果としてユーザーに二重の請求が発生してしまう。これはユーザー体験を著しく損ねるだけでなく、信頼性の低下にもつながる重大な問題である。
この問題を解決するために提案されているのが、「Idempotency-Key(べき等キー)パターン」である。このパターンは、リクエストごとに一意の「べき等キー」をクライアントが生成し、それをHTTPヘッダーに含めてサーバーに送るというものだ。サーバー側では、このべき等キーを受け取ると、まず過去に同じキーを持つリクエストが処理されたかどうかを確認する。
具体的には、サーバーは受け取ったべき等キーと、それに対応する処理結果(レスポンスデータ)をデータベースに保存しておく。次に同じキーを持つリクエストが来た場合、サーバーは決済処理などの実際の操作を再度実行するのではなく、データベースに保存されている過去のレスポンスをそのままクライアントに返す。これにより、クライアントが何度リトライしても、サーバー側の処理は一度しか実行されず、結果も常に同じものが返されるため、二重請求のような意図しない副作用を防ぐことができる。
ASP.NET Coreでの実装例を見ると、PaymentsControllerというAPIコントローラー内にCreatePaymentというHTTP POSTメソッドが定義されていることがわかる。このメソッドは、通常の決済リクエストデータに加えて、HTTPヘッダーからIdempotencyKeyという文字列を受け取るように設計されている。
メソッドの処理フローは以下の通りだ。
まず、_context.IdempotencyKeysというデータベースのテーブルを検索し、受け取ったIdempotencyKeyと一致するレコードがあるかどうかを確認する。もし既存のキーが見つかった場合、それは過去にこのキーでリクエストが処理済みであることを意味する。この場合、決済処理をスキップし、データベースに保存されていたexistingKey.Response(過去の処理結果)をそのままクライアントに返す。これは、記事中でreturn Ok(existingKey.Response);と記述されている部分だ。
もし既存のキーが見つからなかった場合、それは新しいリクエストと判断され、実際の決済処理が実行される(var payment = ProcessPayment(request);)。決済処理が無事完了したら、その結果(payment)と、今回使用したIdempotencyKeyをデータベースに保存する必要がある。ここで重要なのが、決済処理とデータベースへのキーの保存を「アトミック(不可分)」に行うことだ。
アトミックとは、複数の操作が一つの単位として扱われ、全てが成功するか、全てが失敗して元に戻されるかのいずれかであることを意味する。記事ではこれを実現するために、using var transaction = await _context.Database.BeginTransactionAsync();というコードでデータベーストランザクションを開始している。トランザクション内で、IdempotencyKeyのレコードを生成し、_context.IdempotencyKeys.Add(idempotencyKey);で追加し、await _context.Database.SaveChangesAsync();でデータベースに保存する。これらの操作が全て成功したら、await transaction.CommitAsync();で変更を確定する。もし途中で何らかのエラーが発生した場合は、catchブロックでawait transaction.RollbackAsync();を実行し、トランザクション開始前の状態にすべて戻す。これにより、決済は成功したのにキーの保存が失敗したり、その逆の状況が起こるのを防ぎ、データの一貫性を保つことができる。
さらに、このパターンには「競合状態(Race Conditions)」という別の問題が発生する可能性がある。これは、同じIdempotencyKeyを持つ複数のリクエストが、ごく短い時間差でほぼ同時にサーバーに到着した場合に起こる。もしトランザクションを使わずに処理していたら、両方のリクエストが「キーが見つからない」と判断し、それぞれが独立して決済処理を実行し、結果的に二重請求が発生してしまうかもしれない。
この競合状態を防ぐためにも、データベーストランザクションが非常に有効である。記事では、「キーのチェックと挿入をデータベーストランザクションでラップした」と説明している。データベースの排他制御機能により、同じIdempotencyKeyを追加しようとした場合、最初のリクエストだけが成功し、二番目以降のリクエストは、コミット待ちの状態になったり、重複キーエラーになったりする。これにより、複数のリクエストが同時に来ても、実際にキーが挿入され、決済が実行されるのは一度きりとなる。他のリクエストは、その後の処理で既存キーとして検出され、保存された結果を受け取ることになる。
まとめると、べき等性とは、ある操作を複数回実行しても、一度だけ実行した場合と同じ結果になる性質のことである。決済APIのような、状態を変更する重要な操作において、ネットワークの不安定さやクライアントのリトライによって発生しうる二重請求などの問題を未然に防ぐために、このべき等性の設計は不可欠だ。今回紹介されたIdempotency-Keyパターンは、クライアントからのユニークなキーをサーバー側で管理し、永続ストレージ(データベース)とデータベーストランザクションを組み合わせることで、リトライに対する安全性を高め、信頼性の高いAPIを構築するための強力な解決策となる。システムエンジニアとして、APIを設計・開発する際には、特に可変操作においては常にべき等性を意識し、このようなパターンを適用することを心がけるべきである。