従来のCRUDベースのREST APIが複雑なワークフローに対応する際に不足している理由を簡単に振り返りましょう:

  • 状態の表現が不足している
  • 長時間実行されるプロセスの処理が難しい
  • ロールバックや補償トランザクションのサポートが組み込まれていない
  • 複雑なビジネスロジックを表現する能力が限られている

これらの制限は、注文の履行や多段階の承認ワークフロー、または状態を維持し失敗を優雅に処理する必要があるシナリオをモデル化しようとする際に、痛感されます。

イベント駆動型REST APIの登場

では、RESTfulの原則を守りながらこれらの課題にどう対処するのでしょうか?その答えは、API設計にイベント駆動型アーキテクチャを取り入れることにあります。以下のようにアプローチを再考してみましょう:

1. リソース指向の状態マシン

CRUD操作の観点ではなく、リソースを状態マシンとして考えてみましょう。各リソースは有効な状態とそれらの間の遷移を持つことができます。


{
  "id": "order-123",
  "state": "pending",
  "allowedTransitions": ["confirm", "cancel"]
}

このモデルでは、状態遷移がリソースと対話する主な方法となります。これらの遷移をサブリソースやカスタムアクションとして公開することができます。

2. 非同期操作

長時間実行されるプロセスには、非同期操作を実装します。クライアントが複雑なワークフローを開始すると、202 Acceptedステータスと操作の状態を表すリソースを返します。


POST /orders/123/fulfill HTTP/1.1
Host: api.example.com

HTTP/1.1 202 Accepted
Location: /operations/456

クライアントはその後、操作リソースをポーリングしてその状態を確認できます:


{
  "id": "operation-456",
  "status": "in_progress",
  "percentComplete": 75,
  "result": null
}

3. イベントソーシング

イベントソーシングを実装して、状態変化の完全な履歴を保持します。このアプローチにより、監査可能性が向上し、複雑なロールバックシナリオが可能になります。


{
  "id": "order-123",
  "events": [
    {"type": "OrderCreated", "timestamp": "2023-05-01T10:00:00Z"},
    {"type": "PaymentReceived", "timestamp": "2023-05-01T10:05:00Z"},
    {"type": "ShippingArranged", "timestamp": "2023-05-01T10:10:00Z"}
  ],
  "currentState": "shipped"
}

4. 補償ベースのロールバック

複数ステップのプロセスには、補償ベースのロールバックを実装します。プロセスの各ステップには、その効果を元に戻すことができる対応する補償アクションが必要です。


{
  "id": "workflow-789",
  "steps": [
    {"action": "reserveInventory", "compensation": "releaseInventory", "status": "completed"},
    {"action": "chargeCreditCard", "compensation": "refundPayment", "status": "failed"}
  ],
  "currentStep": 1,
  "status": "rolling_back"
}

実践的な実装のヒント

理論をカバーしたところで、これらの概念を実装するための実践的なヒントを見てみましょう:

1. ハイパーメディアコントロールを使用する

HATEOAS(Hypertext As The Engine Of Application State)を活用して、クライアントを複雑なワークフローに導きます。リソースの現在の状態に基づいて可能なアクションへのリンクを含めます。


{
  "id": "order-123",
  "state": "pending",
  "links": [
    {"rel": "confirm", "href": "/orders/123/confirm", "method": "POST"},
    {"rel": "cancel", "href": "/orders/123/cancel", "method": "POST"}
  ]
}

2. リアルタイム更新のためのWebhookを実装する

長時間実行されるプロセスには、Webhookを実装してクライアントに状態変化を通知し、継続的に更新をポーリングする必要をなくします。

3. 冪等性キーを使用する

非同期操作を扱う際には、冪等性キーを使用して、ネットワークの問題やクライアントの再試行による操作の重複を防ぎます。


POST /orders/123/fulfill HTTP/1.1
Host: api.example.com
Idempotency-Key: 5eb63bbbe01eeed093cb22bb8f5acdc3

4. 分散トランザクションのためのサガパターンを実装する

複数のサービスを含む複雑なワークフローには、サガパターンを実装して分散トランザクションとロールバックを管理します。

潜在的な落とし穴

すべてのAPIをリファクタリングする前に、これらの潜在的な課題に注意してください:

  • API設計と実装の複雑さの増加
  • API利用者の学習曲線の高さ
  • イベントストレージと処理によるパフォーマンスのオーバーヘッドの可能性
  • 堅牢なエラーハンドリングと再試行メカニズムの必要性

まとめ

イベント駆動型ワークフローのためのREST APIの設計は、単純なCRUD操作から、状態、非同期プロセス、複雑なビジネスロジックを考慮したより微妙なアプローチへの思考の転換を必要とします。状態マシン、イベントソーシング、補償ベースのロールバックなどの概念を取り入れることで、現実のプロセスをよりよく表現する、より堅牢で柔軟なAPIを作成できます。

目標は、物事を不必要に複雑にすることではなく、RESTfulの原則を守りながら、ビジネスドメインの複雑さに対応できるAPIを作成することです。どのようなアーキテクチャの決定も、特定のユースケースと要件を考慮してから行ってください。

さあ、素晴らしいイベント駆動型APIを設計しましょう!そして、CRUDのシンプルさが恋しくなったら...まあ、GraphQLもあります。でもそれはまた別の話です。

「大規模なアプリを構築する秘訣は、大規模なアプリを決して構築しないことです。アプリケーションを小さな部分に分割します。そして、それらのテスト可能な小さな部分を組み合わせて大きなアプリケーションを作成します。」— ジャスティン・マイヤー

コーディングを楽しんでください!