オーケストレーション vs. コレオグラフィ - イベント駆動型バックエンド連携
Grace Collins
Solutions Engineer · Leapcell

はじめに
現代のバックエンドシステム、特にマイクロサービスアーキテクチャの複雑な世界では、シームレスな通信と堅牢なデータフローが最優先事項です。システムが複雑化・大規模化するにつれて、従来の密結合された連携はボトルネックとなり、アジリティとレジリエンスを妨げることがよくあります。そこで、イベント駆動型アーキテクチャが輝きを放ち、サービス間通信のための柔軟でスケーラブルなパラダイムを提供します。このパラダイムの中で、複雑なビジネスプロセスを調整するための2つの主要なパターンが現れます:オーケストレーションとコレオグラフィです。それぞれのニュアンス、利点、トレードオフを理解することは、パフォーマンスが高く保守性の高い分散システムを構築することを目指すアーキテクトや開発者にとって不可欠です。この記事では、これら2つの強力なアプローチを解明し、実際のシナリオでの適用方法を明確に示します。
イベント駆動型システムにおける調整の解明
オーケストレーションとコレオグラフィの詳細に入る前に、イベント駆動型アーキテクチャの基盤を形成する主要な用語について共通の理解を確立しましょう。
- イベント: 過去に起こったことの記録。「OrderCreated」(注文作成)や「PaymentProcessed」(支払い処理済み)のような、不変の事実です。通常、状態変化は含まれますが、それに対応するためのロジックは含まれません。
 - マイクロサービス: 単一のビジネス機能を実行する、小さく自律的なサービスです。マイクロサービスは、しばしばイベントを通じて互いに通信します。
 - メッセージブローカー(またはイベントバス): メッセージ/イベントを受信、キューイング、配信することで、サービス間の通信を促進するミドルウェアです。例としては、Apache Kafka、RabbitMQ、AWS SQS/SNSなどがあります。
 - 分散トランザクション: 複数の独立したサービスが関与するトランザクションです。このようなトランザクションでアトミシティ(すべて成功またはすべて失敗)を保証することは、Sagaパターンのようなパターンで解決されることが多い、重大な課題です。
 
オーケストレーション:中央指揮者
バックエンドシステムにおけるオーケストレーションは、オーケストラの指揮者が楽団を率いることに例えることができます。オーケストレーターと呼ばれる中央サービスが、ビジネスプロセス全体のフローを指示する責任を負います。操作のシーケンスを管理し、他のサービスにコマンドを発行し、続行する前にそれらの応答を待ちます。オーケストレーターは、プロセスの全体像を把握し、その実行を積極的に制御します。
原則:
- 集中制御: 単一のサービスがワークフローの決定を担当します。
 - コマンド駆動: オーケストレーターは、他のサービスに明示的なコマンドを送信します。
 - ステートフル: オーケストレーターは、多くの場合、全体的なプロセスの状態を維持します。
 
実装:
eコマースの注文履行プロセスを考えてみましょう。OrderService がオーケストレーターとして機能する可能性があります。
// OrderServiceImpl.java (Orchestrator) @Service public class OrderServiceImpl implements OrderService { @Autowired private PaymentClient paymentClient; // PaymentServiceへのRESTクライアント @Autowired private InventoryClient inventoryClient; // InventoryServiceへのRESTクライアント @Autowired private ShippingClient shippingClient; // ShippingServiceへのRESTクライアント @Override public Order createOrder(OrderRequest request) { // 1. 注文レコードを作成 Order order = saveOrder(request); try { // 2. 支払い処理(コマンドを送信し、応答を待つ) PaymentResponse paymentResponse = paymentClient.processPayment(order.getOrderId(), request.getAmount()); if (!paymentResponse.isSuccess()) { throw new PaymentFailedException("Payment failed for order: " + order.getOrderId()); } // 3. 在庫を控除(コマンドを送信し、応答を待つ) InventoryResponse inventoryResponse = inventoryClient.deductInventory(order.getOrderId(), order.getItems()); if (!inventoryResponse.isSuccess()) { throw new InventoryFailedException("Inventory deduction failed for order: " + order.getOrderId()); } // 4. 出荷を開始(コマンドを送信、非同期応答で十分な場合が多い) shippingClient.initiateShipping(order.getOrderId(), order.getCustomerAddress()); // 5. 注文ステータスを更新して返す order.setStatus(OrderStatus.COMPLETED); return updateOrder(order); } catch (Exception e) { // 障害の処理:補償トランザクション(Sagaパターン) // 例:支払いの払い戻し、在庫の復元 refundPayment(order.getOrderId()); restoreInventory(order.getOrderId(), order.getItems()); order.setStatus(OrderStatus.FAILED); updateOrder(order); throw new OrderProcessingException("Order processing failed", e); } } // ... 保存、更新、補償アクションのヘルパーメソッド }
この例では、OrderService が PaymentService、InventoryService、ShippingService に直接、定義されたシーケンスで呼び出します。フローを管理し、補償アクション(オーケストレーションで一般的に使用されるSagaパターンの形式)を開始することによって潜在的な障害を処理します。
適用シナリオ:
- 厳密な順序付けを伴う複雑なビジネスプロセス: 操作のシーケンスが重要であり、厳密に強制される必要がある場合。
 - ワークフローエンジン: CamundaやNetflix Conductorのようなシステムは、オーケストレーションの原則に基づいて構築されています。
 - プロセスの状態の明確で集中的なビューが必要な場合: 全体的なフローのデバッグや監視が容易になります。
 
コレオグラフィ:分散ダンス
対照的に、コレオグラフィは、中央のディレクターなしに、互いの合図に反応し、自分の動きを知っているダンサーのグループに似ています。各サービスは自律的に動作し、興味深いことが起こるとイベントを発行し、他のサービスによって発行されたイベントに反応します。全体のフローを指示する単一のサービスはなく、参加サービス個々の独立した反応から全体的なプロセスが生まれます。
原則:
- 分散制御: 単一のサービスがプロセス全体をオーケストレーションすることはありません。
 - イベント駆動: サービスはイベントを発行し、イベントに反応します。
 - ステートレス(全体的なプロセスに関して): 個々のサービスが自身の状態を管理します。
 
実装:
イベントバス(例:Kafka)を活用して、コレオグラフィを使用した eコマース注文履行を再度検討しましょう。
// OrderService.java (Publisher) @Service public class OrderService { @Autowired private KafkaTemplate<String, OrderCreatedEvent> kafkaTemplate; public Order createOrder(OrderRequest request) { Order order = saveOrder(request); // 初期注文状態を保存 // OrderCreatedイベントを発行 OrderCreatedEvent event = new OrderCreatedEvent(order.getOrderId(), order.getCustomerId(), order.getAmount(), order.getItems()); kafkaTemplate.send("order-events", event.getOrderId().toString(), event); return order; } } // PaymentService.java (Consumer) @Service public class PaymentService { @Autowired private KafkaTemplate<String, PaymentProcessedEvent> kafkaTemplate; @Autowired private KafkaTemplate<String, PaymentFailedEvent> kafkaTemplateFailure; @KafkaListener(topics = "order-events", groupId = "payment-group", containerFactory = "kafkaListenerContainerFactory") public void handleOrderCreated(OrderCreatedEvent event) { try { // 支払い処理ロジック boolean success = processPayment(event.getOrderId(), event.getAmount()); if (success) { // PaymentProcessedイベントを発行 PaymentProcessedEvent processedEvent = new PaymentProcessedEvent(event.getOrderId(), event.getAmount(), PaymentStatus.SUCCESS); kafkaTemplate.send("payment-events", processedEvent.getOrderId().toString(), processedEvent); } else { // PaymentFailedイベントを発行 PaymentFailedEvent failedEvent = new PaymentFailedEvent(event.getOrderId(), "Insufficient funds"); kafkaTemplateFailure.send("payment-failure-events", failedEvent.getOrderId().toString(), failedEvent); } } catch (Exception e) { // ログ記録と、場合によっては障害イベントの発行 } } // ... processPaymentロジック } // InventoryService.java (Consumer) @Service public class InventoryService { @Autowired private KafkaTemplate<String, InventoryDeductedEvent> kafkaTemplate; @Autowired private KafkaTemplate<String, InventoryFailedEvent> kafkaTemplateFailure; @KafkaListener(topics = "payment-events", groupId = "inventory-group", containerFactory = "kafkaListenerContainerFactory") public void handlePaymentProcessed(PaymentProcessedEvent event) { // InventoryServiceは自身のストアまたは別のイベントを通じて注文詳細を取得できると仮定 OrderDetails orderDetails = fetchOrderDetails(event.getOrderId()); // 詳細を取得するための独立した方法が必要 try { boolean success = deductInventory(event.getOrderId(), orderDetails.getItems()); // 控除ロジック if (success) { // InventoryDeductedイベントを発行 InventoryDeductedEvent deductedEvent = new InventoryDeductedEvent(event.getOrderId(), orderDetails.getItems()); kafkaTemplate.send("inventory-events", deductedEvent.getOrderId().toString(), deductedEvent); } else { // InventoryFailedイベントを発行(場合によっては、PaymentRefundRequestedEventのような補償アクションも) InventoryFailedEvent failedEvent = new InventoryFailedEvent(event.getOrderId(), "Out of stock"); kafkaTemplateFailure.send("inventory-failure-events", failedEvent.getOrderId().toString(), failedEvent); } } catch (Exception e) { // ログ記録と、場合によっては障害イベントの発行 } } // ... deductInventoryロジック }
このコレオグラフィの例では、OrderService は単に OrderCreatedEvent を発行します。PaymentService は OrderCreatedEvent をリッスンし、支払いを行い、PaymentProcessedEvent(または PaymentFailedEvent)を発行します。次に InventoryService は PaymentProcessedEvent をリッスンし、在庫を控除し、InventoryDeductedEvent(または InventoryFailedEvent)を発行します。各サービスは、消費するイベントに基づいて独立して動作します。
適用シナリオ:
- 疎結合システム: サービスが真に独立して動作し、あるサービスでの障害が他のサービスを直接停止させない場合。
 - スケーラビリティとレジリエンス: 個々のサービスを簡単にスケーリングでき、コンポーネント障害を適切に処理できます。
 - 頻繁に進化する複雑なプロセス: あるサービスロジックの変更は、他のサービスへの影響が少なくなります。
 - イベントソーシングアーキテクチャ: 状態変更をすべて不変のイベントとして保存するという概念に自然に適合します。
 
オーケストレーションとコレオグラフィの比較:主要な考慮事項
| 特徴 | オーケストレーション | コレオグラフィ | 
|---|---|---|
| 制御フロー | 集中型、明示的 | 分散型、暗黙的(創発的) | 
| 結合度 | よりタイト(オーケストレーターは参加者を意識) | よりルーズ(サービスは消費/生成するイベントのみを意識) | 
| 可視性 | 高(オーケストレーターはプロセス全体を理解) | 低(単一のサービスがプロセス全体を見ることはない) | 
| 複雑さ | オーケストレーターが複雑になる可能性(ゴッドオブジェクト) | 個々のサービスはシンプルだが、全体的なプロセスの可視化が困難 | 
| 障害処理 | 補償アクション(Saga)の実装が容易 | 分散トレーシングとイベントリプレイが必要、ロールバックの調整がより複雑 | 
| スケーラビリティ | オーケストレーターがボトルネックになる可能性 | サービスが独立して動作するため、非常にスケーラブル | 
| 進化 | ワークフローの変更は主にオーケストレーターに影響 | 中央ロジックに影響を与えずにステップを追加/削除するのが容易 | 
結論
オーケストレーションとコレオグラフィはいずれも、堅牢なイベント駆動型バックエンドシステムを構築するための強力なパターンです。オーケストレーションは、複雑なワークフローに対する明確で集中化されたビューと制御を提供し、厳密な順序付け要件を持つプロセスや、全体的な状態の強い理解が必要な場合に適しています。しかし、オーケストレーターが複雑になりすぎると、単一障害点やボトルネックになる可能性があります。
一方、コレオグラフィは、サービスがイベントに自律的に反応できるようにすることで、より大きな疎結合性、スケーラビリティ、レジリエンスを促進します。この分散型アプローチはシステムを変化に適用しやすくしますが、ビジネスプロセスのエンドツーエンドのフローを tracing したり、包括的なエラー処理を実装したりすることが困難になる可能性があります。
オーケストレーションとコレオグラフィの選択は相互排他的ではなく、多くの場合、ビジネスプロセスの特定のコンテキストに依存します。大規模システムでよく見られるアプローチは、ハイブリッドモデルを使用することです。高レベルでクリティカルなワークフローにはオーケストレーションを、より小さく独立したサブプロセスにはコレオグラフィを使用します。最終的な目標は、柔軟性、レジリエンス、保守性を備え、ビジネスの進化する要求に対応できるシステムを構築することです。真のアートは、各インタラクションの特定のニーズに最適なパターンを識別し、バックエンドサービスに対して制御と自律性の適切なバランスをとることにあるのです。

