0. 前提
実際の返金処理は決済サービス側で行い、APIでは返金日時をセットし
「日時が存在すれば返金済と扱う」という仕様に決まったのですが、
開発チームで「返金処理のAPIをどう設計するか」でめちゃくちゃ熱い議論になりました。
DBのカラム一つを更新するだけなのに、なぜか既定の設計案に「個人的にやや違和感がある」。
このモヤモヤを解消するために、どういう考え方をすればいいのか、結論に至るまでの葛藤も含めてまとめます。
1. 😓 モヤモヤの始まり(これが元の設計案)
まず、叩き台になったのはこの設計。paymentテーブルのrefund_atカラムを操作する想定です。
| 操作 | エンドポイント | メソッド | 実装(裏側) |
|---|---|---|---|
| 返金実行 | /payment/:id/refund |
PATCH |
refund_atに日時を入れる |
| 返金取消 | /payment/:id/refund/cancel |
PATCH |
refund_atをnullにする |
2. 🤔 なぜこんなに違和感があるのか(議論の葛藤)
多くのメンバーが抱えたモヤモヤは、この2択で揺れ動いていました。
A. 効率重視派(PATCH /payment/:id)
「裏側でやってるのは結局DBのカラムを更新してるだけ。なら、汎用的な PATCH /payment/:id でrefund_atを更新(nullも含む)すればシンプルで良くない?」
懸念点: 他の項目更新とごっちゃになって、返金という重要な操作の存在感が薄くなる。
B. アクション重視派(POST/DELETE /refund)
「『返金』は単なるデータ更新じゃなく、業務上のイベントだよね? だからアクションを示す POST(実行)と DELETE(取り消し)に分けたい!」
懸念点:
POSTなのにリソースを作らない、DELETEなのに物理削除しないのは、APIとして気持ち悪くないか?(これは「論理削除にDELETEを使うのはアリ」という標準的なプラクティスで解決しました)
結局のところ、APIを「データベースの状態を表現するもの」と捉えるか、「業務上のアクションを表現するもの」と捉えるか、という設計思想の対立だったわけです。
3. ✨ 結論:アクションをリソースとして表現する(POST/DELETE採用!)
悩んだ末、私たちは**「業務アクション重視」**のB案を採用することに決めました。
たとえ裏側でカラムをいじっているだけであっても、APIの利用者に「これは重要な操作だ」と伝える設計を選びました。
| 操作 | エンドポイント | メソッド | 意味合い |
|---|---|---|---|
| 返金実行 | /payment/:id/refund |
POST |
「返金済み状態」というリソースの作成 |
| 返金取消 | /payment/:id/refund |
DELETE |
「返金済み状態」というリソースの削除(取り消し) |
この設計に決めた、決定的な理由3つ
-
業務ログの追跡がしやすい
「返金処理が行われた」ログを追うとき、PATCH /payment/123だと何が更新されたかBodyを見ないと分かりません。しかし、POST /payment/123/refundなら一発で「返金イベントだ」と特定できます。運用上のメリットがデカい! -
権限の分離がしやすい
「住所変更はできるけど、返金操作はできない」といった、機密性の高い操作に対する権限管理を、/refundのエンドポイント単位でガチガチに設定できます。 -
REST原則に則っている
PATCHは部分更新、POST/DELETEはリソースの作成/消滅(論理的消滅も含む)を表します。動詞的な「返金」アクションを表現するには、この形が一番自然で違和感がないという結論になりました。
🚀 まとめ
APIを設計するときは、「DBのカラム」を意識するのを一度やめて、「この操作はビジネス上どんな意味を持つイベントか?」という視点に切り替えてみると、スッキリした設計にたどり着きやすいです。
重要なアクションには、汎用的なPATCHではなく、専用のPOSTやDELETEを用意して、そのアクションの重みをAPIの形でも表現してあげましょう!