🤢
システム間で整合をとるために考えること
バニッシュ・スタンダードでアーキテクトやってる小林です。
他システムのAPI叩いたりとかする場合に、自システムと他システム間の整合が取れなくなる可能性が発生することがたまにありますよね?決済してお金はもらったけど、買ったことになってないとか。逆に買ったことになってるけど決済されてないとか。そういう場合にどうしましょう?っていうのを考えてみます。
下にも書いてますが、唯一の正解があるとかではなく、こんな感じで考えていくと良い落とし所が見つかるんじゃないか?という一例です。弊社はECサイトの開発をやってるわけではないのでこの通りのことは発生しませんが、わかりやすい例ということでECサイトをあげました。
システム構成
今回は仮にECサイト(自システム)と決済システムと在庫&発送システム(他システム)としましょう。役割は以下とします。
ECサイト
- カートに入れる
- 注文する
- 決済を依頼する
- 決済できたら商品を発送する
決済システム
- 決済する
在庫&発送システム
- 在庫を管理する
- 商品を発送する
外部システムのAPI
特に具体的などれを参考にしてるとかではなく、あくまで仮定のシステムですが以下のAPIがあるとします。
決済システム
authory状態で一定時間過ぎるとキャンセルされる仕組みとします
- authory(オーソリ)
- request
- request_id(一意になる値)
- user_id
- amount
- response
- transaction_id
- error(パラメータエラーやサーバーエラー以外)
- リクエスト受付済み
- request
- confirm(確定)
- request
- transaction_id
- response
- empty(特に値は返さず、http_status_codeを返す)
- error(パラメータエラーやサーバーエラー以外)
- リクエスト受付済み
- request
- cancel(キャンセル)
- request
- request_id
- response
- empty(特に値は返さず、http_status_codeを返す)
- error(パラメータエラーやサーバーエラー以外)
- リクエスト受付済み
- request
在庫&発送システム
reserve状態で一定時間過ぎるとキャンセルされる仕組みとします
- reserve(在庫引当)
- request
- request_id(一意になる値)
- user_id
- product_id
- count
- response
- transaction_id
- error(パラメータエラーやサーバーエラー以外)
- リクエスト受付済み
- request
- confirm(確定して発送する)
- request
- transaction_id
- response
- empty(特に値は返さず、http_status_codeを返す)
- error(パラメータエラーやサーバーエラー以外)
- リクエスト受付済み
- request
- cancel(キャンセル)
- request
- request_id
- response
- empty(特に値は返さず、http_status_codeを返す)
- error(パラメータエラーやサーバーエラー以外)
- リクエスト受付済み
- 確定処理済み
- request
処理シーケンス
まずはこんなシーケンスになってくるのではないかと思います。
問題点
とりあえずシーケンスではいくつか問題があります。
- カートに入れてすぐ購入に至るとは限らず、この間が長い場合に在庫を引き当てたままとなるので、その間売ることができない商品が存在することになり、機会損失となります。
- 在庫の引き当てができたかどうか、返却を受け取るまでわからなく、途中で受け取れない事象が発生した場合に再処理等ができない
- ③を受け取るときにタイムアウトしたが在庫&発送システムは処理完了している
- ④の保存で問題があった
- オーソリができたかどうか、返却を受け取るまでわからなく、途中で受け取れない事象が発生した場合に再処理等ができない
- ⑧を受け取るときにタイムアウトしたが決済システムは処理完了している
- ⑨の保存で問題があった
- 決済確定したかどうか、返却を受け取るまでわからなく、途中で受け取れない事象が発生した場合に再処理等ができない
- ⑪を受け取るときにタイムアウトしたが決済システムは処理完了している
- ⑫の保存で問題があった
- 確定処理が受け付けられたかどうか、返却を受け取るまでわからなく、途中で受け取れない事象が発生した場合に再処理等ができない
- ⑭を受け取るときにタイムアウトしたが決済システムは処理完了している
- ⑮の保存で問題があった
解決策
それぞれ唯一の解があるわけじゃなくそれぞれメリデメあるので、案件ごとに決める必要はあります。要はバランス。
問題点1
- 決済時に在庫引き当てる
- デメリット
- カートに入ったのに買えないという事象が発生しうる
- 対応
- カートに入れる際に注意書きを入れる
- デメリット
問題点2,3
- 処理前にrequest_idをキーに処理中として保存し、レスポンスを受け取ったら処理完了として保存、定期バッチで途中のままのデータはキャンセルを行う ←◯採用
- デメリット
- 在庫が引き当てられたまま、オーソリされたまま(与信枠確保されたまま)となる期間が発生しうる
- デメリット
問題点4
- 処理前にrequest_id or transaction_idをキーに処理中として保存し、レスポンスを受け取ったら処理完了して保存する、定期バッチで途中のままのデータはキャンセルを行う
- デメリット
- 決済はいったんされてしまう可能性(決済システムから通知が飛んだり)
- デメリット
- 処理受け付けた時点で受付完了なステータスにしておき、非同期で確定処理を行い。完了もしくは失敗時点で完了の通知を行う。途中状態になった場合は再処理をする ←◯採用
- デメリット
- 決済できたかできてないのか中途半端な状態を見せることになる
- デメリット
問題点5
- 処理前にrequest_id or transaction_idをキーに処理中として保存し、レスポンスを受け取ったら処理完了して保存する、定期バッチで途中のままのデータはキャンセルを行う
- デメリット
- 決済はされているので決済からキャンセルしないといけない
- デメリット
- 処理受け付けた時点で受付完了なステータスにしておき、非同期で確定処理を行い。完了もしくは失敗時点で完了の通知を行う ←◯採用
- デメリット
- 注文完了できたかできてないのか中途半端な状態を見せることになる
- デメリット
処理シーケンス(改善後)
上記の案を採用したシーケンスは以下になります。(最後急に通知が出て来ましたが許してください)
まとめ
もうちょっと細かいところを見ていくともっと救わないといけないパターンも出てきますが、いったん考え方としてこんなことを考えながらやってます。
Discussion