結論
複数リソースの処理がどうしても必要(それがAPIで提供すべきサービスである)場合、割り切って一連のトランザクション処理を1つのAPIサービスとして提供しましょう。
オライリーの「RESTful WEBサービス」1にもある『複数リソースにまたがる処理』の例として、銀行業務における当座預金から普通預金への口座振替処理が挙げられています。
本著ではトランザクション処理そのものをリソースとして提供するアイデアを解決策として記載しています。
本ケースでの私なりの結論は『/account-transfer
(口座振替)自体を1つのAPIとして提供する。』です。そうすることでAPI処理もシンプルになり、API利用側も直観的で使いやすくなると考えています。
例えば以下のようなAPIです。
POST /accounts/:account-id/banks/:from-bank-id/account-transfer/:to-bank-id
HOST: example.com
amount=100
理由
そもそもRESTとは
「REST(REpresentational State Transfer)」(レスト)
は HTTPのプロトコル設計に準ずることでAPI仕様の統一を図る基本的な考え方・原則 であり、DBテーブルのCRUDを単機能APIで提供することではありません。
※割と勘違いしがちと勝手に思っています。
簡単に、ではありますがRESTの原則は以下の認識です。
- アドレス指定可能なURIでリソースを公開
- 統一IF(HTTPメソッドで操作を決定し、処理結果はHTTPステータスコードで表す)
- ステートレス(リクエスト毎に処理を完結させる)
- リンク情報を情報に含む
リソース=DBテーブルではない。
では、公開するリソース
とは何を指すか。
「RESTful WEBサービス」の記載から引用します。
サービスが提供する「もの」ごとにリソースが1つ必要である。「リソース」は「もの」と同じくらいあいまいなので、あらゆるデータやアルゴリズムをリソースにすることができる。
私なりの理解ですが、APIサービスとして提供するべきもの=リソースと理解しています。
WEB APIを公開するということは、利用者と用途が想定されているものです。
APIシステム側で提供するべきサービスを検討した結果、単純なCRUD APIになることもあれば、先の口座送金処理のように業務処理を提供するAPIになることもあるという理解です。
補足
リソース設計次第でトランザクション制御が不要となる可能性はあります。
なにも考えずにファットなAPI提供をすればよいと言っているわけではなく、提供すべきAPIとは何かを考えた結果、API処理にトランザクション制御が必要であれば素直に実装した方がAPIがシンプルになり、利用者側・提供側の双方にメリットがあるということです。
トランザクションリソースについて
先の口座振替処理で触れた「RESTful WEBサービス」の『トランザクション自体をリソースとして提供する』方式について考えたいと思います。
まず、トランザクション作成用URIにPOSTして、トランザクション自体を作成します。
POST /transactions/account-transfer HTTP/1.1
HOST: example.com
レスポンスにはトランザクションリソースのURIが含まれています。
HTTP/1.1 201 Created
Location: /transactions/account-transfer/11a5
作成されたトランザクションを指定して預金の振替内容をPUTしていきます。
PUT /transactions/account-transfer/11a5/accounts/checking/11 HTTP/1.1
HOST: example.com
balance=150
PUT /transactions/account-transfer/11a5/accounts/saving/55 HTTP/1.1
HOST: example.com
balance=250
内容のPUTが完了したらトランザクションリソースのコミットを行います。
PUT /transactions/account-transfer/11a5 HTTP/1.1
HOST: example.com
commited=true
コミット処理が成功するとPUTリクエストの応答として、処理結果を得られます。
HTTP/1.1 200 OK
Content-Type: application/xhtml+xml
・・・
<a href="/accounts/checking/11">Checking #11</a>: New balance $150
<a href="/accounts/checking/11">Savings #55</a>: New balance $250
・・・
この時点で処理が確定しロールバック不可となります。
当然コミット処理では業務バリデーションなどを実施して、データ整合性を取りながら確定処理を行います。
補足
実現方法としては、一連の更新リクエストをトランザクションリソース毎にキューイングするようなことが必要です。
動的キューイングを実装しようとすると割と面倒ですが、AWS SQSの一時キューなど利用すれば実装も楽になるかなと思います。
トランザクション自体をリソースとして提供することでREST原則にもあるリンクが可能になることが利点でしょうか。
RESTに則るという意味では綺麗に見えますし、1つの案としてはいいのかもしれません。
API提供側の責任は?
ただそれはReactやVue.jsで実装されたWEBアプリのバックエンドAPIを実装する際など、API利用者を信頼できる場合に限るのではないかと私は考えます。
(そもそもフロント側に業務ロジックを任せるのであればAPIをシステムとして立ち上げるのではなくAmplifyか何かで作成してしまう選択もあると考えます)
外部サービスとして公開するWEB APIや社内の別システムとの連携に利用するAPIであれば、 更新後のデータの整合性を担保するのはAPI提供システムの責任 になるので、外部に対してトランザクション制御を任せる方式は現実的に選択肢にならないでしょう。
APIのユースケースを想定して設計する
当然ですがAPIはそれだけではサービスとしての価値がなく、誰かに利用されて初めて価値がでるものです。
RESTという枠組みにとらわれすぎず(そもそも枠組みとしては寛容なのですが、寛容すぎて、こうあるべきという強硬な意思を持つ方が多いイメージ・・)、また、システムのデータモデルにもとらわれず、APIとしての価値を高める設計をすべきと思います。
RESTという枠組みを活かしつつ、もっとも使いやすいAPIは何かを各々が試行錯誤して作成すべきと思います。
私自身も常に悩みながら試行錯誤を繰り返しています。
『使いやすいAPIは何か』を考える際に、オライリーの「Web API: The Good Parts」2はすごく参考になるのでおすすめです。
最後に
割と私見が多く含まれた記事になってしまいましたが、API設計に悩んでいる方の参考になれば幸いです。
反対意見やアドバイス等あれば気軽にコメントをください。
-
オライリー「RESTful Webサービス」
https://www.oreilly.co.jp/books/9784873113531/ ↩ -
オライリー「Web API: The Good Parts」
https://www.oreilly.co.jp/books/9784873116860/ ↩