中央の制御ポイントに依存するのではなく、システムの各コンポーネントが、ビジネス トランザクションのワークフローに関する意思決定プロセスに参加するようにします。
コンテキストと問題
マイクロサービス アーキテクチャでは、クラウドベースのアプリケーションを複数の小さなサービスに分割し、それらのサービスが連携して、ビジネス トランザクションをエンドツーエンドで処理することがよくあります。 サービス間の結合を弱くするために、各サービスは 1 つのビジネス操作を担当します。 開発の高速化、コード ベースの縮小、スケーラビリティなどの利点があります。 ただし、効率的でスケーラブルなワークフローの設計は難しく、複雑なサービス間通信が必要になることがよくあります。
サービスは、明確に定義された API を使用して、互いに通信します。 1 つのビジネス操作の結果として、すべてのサービス間で複数のポイントツーポイント呼び出しが発生する可能性もあります。 通信の一般的なパターンは、オーケストレーターとして機能する集中化されたサービスを使用するというものです。 すべての受信要求を確認し、それぞれのサービスに操作を委任します。 その過程で、ビジネス トランザクション全体のワークフローも管理します。 各サービスは操作を完了するだけであり、ワークフロー全体は認識しません。
オーケストレーター パターンはサービス間のポイントツーポイント通信を削減しますが、オーケストレーターと、ビジネス トランザクションの処理に参加する他のサービスとの間の密接な結合に起因するいくつかの欠点があります。 タスクを順番に実行するために、オーケストレーターには各サービスの責任に関する一定の領域知識が必要です。 サービスを追加または削除する場合、既存のロジックが壊れるため、通信パスの一部を再設計する必要があります。 適切に設計されたオーケストレーターを使用すれば、ワークフローの構成や、サービスの追加または削除が簡単に行えますが、そのような実装は複雑であり、メンテナンスが困難です。
解決策
中央のオーケストレーターに依存するのではなく、業務をいつどのように処理するかを各サービスで決定できます。
コレオグラフィを実装する 1 つの方法は、非同期メッセージング パターンを使用してビジネス操作をコーディネートすることです。
クライアント要求はメッセージ キューにメッセージを発行します。 メッセージが到着すると、そのメッセージに関心のあるサブスクライバーまたはサービスにプッシュされます。 サブスクライブされた各サービスは、メッセージの指示どおりに操作を実行し、メッセージ キューへの応答で操作の成功または失敗を報告します。 成功した場合、必要な場合に別のサービスがワークフローを継続できるよう、サービスは同じキューまたは異なるメッセージ キューにメッセージをプッシュ バックできます。 操作が失敗した場合、メッセージ バスはその操作を再試行できます。
このようにして、サービスは、オーケストレーターに依存したり、サービス間で直接通信したりすることなく、サービス間でワークフローの "振り付け" を行います。
ポイントツーポイント通信がないため、このパターンはサービス間の結合を弱めるのに役立ちます。 また、すべてのトランザクションを扱う必要がある場合に、オーケストレーターに起因するパフォーマンスのボトルネックを解消できます。
このパターンを使用する状況
新しいサービスを頻繁に更新、削除、または追加することが予想される場合、コレオグラフィ パターンを使用します。 より少ない労力で、既存のサービスを極力中断せずに、アプリ全体を変更できます。
中央のオーケストレーターでパフォーマンスのボトルネックが発生する場合は、このパターンを検討してください。
このパターンはサーバーレス アーキテクチャの自然なモデルであり、すべてのサービスを短期型またはイベント駆動型にすることができます。 イベントのためにサービスを生成し、サービスのタスクを実行させて、タスクが終了したらサービスを削除することができます。
問題と注意事項
オーケストレーターを非集中化すると、ワークフローの管理中に問題が発生する可能性があります。
サービスがビジネス操作を完了できない場合に、その障害からの復旧が難しい場合があります。 1 つの方法は、イベントを発生させることによってサービスが障害を示すようにする、というものです。 障害を示すイベントをサブスクライブしている別のサービスは、補償トランザクションを適用するなどの必要なアクションを実行して、要求内の成功した操作を元に戻します。 障害が発生したサービスは、障害のイベントを発生させることにも失敗する場合があります。 その場合は、再試行やタイムアウトのメカニズムを使用して、その操作を失敗と認識することを検討してください。 例については、「例」のセクションを参照してください。
独立したビジネス操作を並列で処理する場合、ワークフローを実装するのは簡単です。 単一のメッセージ バスを使用できます。 しかし、コレオグラフィを順番に実行する必要がある場合、ワークフローは複雑になる可能性があります。 たとえば、サービス C は、サービス A とサービス B が正常に操作を完了した後でないと操作を開始できません。 1 つのアプローチは、必要な順序でメッセージを取得する複数のメッセージ バスを設けることです。 詳細については、「例」のセクションを参照してください。
サービスの数が急速に増加する場合、コレオグラフィ パターンは困難になります。 独立した移動する要素の多さを考慮すると、サービス間のワークフローは複雑になる傾向があります。 また、分散トレースは困難になります。
オーケストレーターはワークフローの回復性を集中的に管理し、単一障害点になる可能性があります。 これに対して、コレオグラフィの場合は、役割がすべてのサービス間で分散されるため、回復性の堅牢さが低下します。
各サービスは、その操作の回復性だけでなくワークフローにも責任を負います。 この責任はサービスにとって負担になり、実装が難しいことがあります。 各サービスは必要に応じて、要求が正常に終了するように、一時的、非一時的、およびタイムアウトのエラーを再試行する必要があります。 またサービスは、他のサービスがそれに応じた対応ができるよう、操作の成功または失敗をできるだけ伝達する必要があります。
例
この例は、Drone Delivery アプリでのコレオグラフィ パターンを示しています。 クライアントが集荷を要求すると、アプリはドローンを割り当ててクライアントに通知します。
このパターンの例は GitHub にあります。
1 つのクライアント ビジネス トランザクションには、荷物の作成または更新、荷物を配達するドローンの割り当て、配達ステータスの確認という 3 つの異なるビジネス操作が必要です。 これらの操作は 3 つのマイクロサービスによって実行されます。Package、Drone Scheduler、および Delivery の各サービスです。 中央のオーケストレーターの代わりに、サービス同士がメッセージングを使用して連携し、サービス間で要求を調整します。
デザイン
ビジネス トランザクションは複数のホップを通じて順番に処理されます。 各ホップにメッセージ バスとそれぞれのビジネス サービスがあります。
クライアントが HTTP エンドポイントを通じて配達要求を送信すると、Ingestion サービスがそれを受信し、操作イベントを発生させてメッセージ バスに送信します。 バスはサブスクライブしているビジネス サービスを呼び出して、POST 要求でイベントを送信します。 イベントを受信すると、ビジネス サービスが操作を完了して成功または失敗を返す場合と、要求がタイムアウトになる場合があります。成功した場合、サービスは Ok ステータス コードでバスに応答し、新しい操作イベントを発生させて、次のホップのメッセージ バスに送信します。 失敗またはタイムアウトの場合、サービスは、元の POST 要求を送信したメッセージ バスに BadRequest コードを送信することによって、失敗を報告します。 メッセージ バスは再試行ポリシーに基づいて操作を再試行します。 その期限を過ぎると、メッセージ バスは失敗した操作にフラグを付け、要求全体はそれ以上処理されなくなります。
このワークフローは、要求全体が処理されるまで続きます。
この設計では、複数のメッセージ バスを使用してビジネス トランザクション全体を処理します。 Microsoft Azure Event Grid はメッセージング サービスを提供します。 アプリは、同じポッドに 2 つのコンテナーがある Azure Kubernetes Service (AKS) クラスターにデプロイされます。 1 つのコンテナーは Event Grid と対話するアンバサダーを実行し、もう 1 つはビジネス サービスを実行します。 同じポッドに 2 つのコンテナーを置くアプローチにより、パフォーマンスとスケーラビリティが向上します。 アンバサダーとビジネス サービスは同じネットワークを共有するので、短い待機時間と高いスループットが実現します。
処理を重複させることがある再試行操作のカスケードを避けるために、ビジネス サービスの代わりに Event Grid のみが操作を再試行します。 配信不能キュー (DLQ) にメッセージを送信することによって、失敗した要求にフラグを付けます。
再試行操作によってリソースが重複しないことを保証するために、ビジネス サービスの結果は常に同じです。 たとえば、Package サービスは upsert 操作を使用してデータをデータ ストアに追加します。
例では、すべてのサービスおよび Event Grid ホップにわたって呼び出しを相互に関連させるためのカスタム ソリューションを実装します。
すべてのビジネス サービス間のコレオグラフィ パターンを示すコード例を次に示します。 これは、Drone Delivery アプリのトランザクションのワークフローを示しています。 簡潔にするため、例外処理とログ記録のコードは省略しています。
[HttpPost]
[Route("/api/[controller]/operation")]
[ProducesResponseType(typeof(void), 200)]
[ProducesResponseType(typeof(void), 400)]
[ProducesResponseType(typeof(void), 500)]
public async Task<IActionResult> Post([FromBody] EventGridEvent[] events)
{
if (events == null)
{
return BadRequest("No Event for Choreography");
}
foreach(var e in events)
{
List<EventGridEvent> listEvents = new List<EventGridEvent>();
e.Topic = eventRepository.GetTopic();
e.EventTime = DateTime.Now;
switch (e.EventType)
{
case Operations.ChoreographyOperation.ScheduleDelivery:
{
var packageGen = await packageServiceCaller.UpsertPackageAsync(delivery.PackageInfo).ConfigureAwait(false);
if (packageGen is null)
{
//BadRequest allows the event to be reprocessed by Event Grid
return BadRequest("Package creation failed.");
}
//we set the event type to the next choreography step
e.EventType = Operations.ChoreographyOperation.CreatePackage;
listEvents.Add(e);
await eventRepository.SendEventAsync(listEvents);
return Ok("Created Package Completed");
}
case Operations.ChoreographyOperation.CreatePackage:
{
var droneId = await droneSchedulerServiceCaller.GetDroneIdAsync(delivery).ConfigureAwait(false);
if (droneId is null)
{
//BadRequest allows the event to be reprocessed by Event Grid
return BadRequest("could not get a drone id");
}
e.Subject = droneId;
e.EventType = Operations.ChoreographyOperation.GetDrone;
listEvents.Add(e);
await eventRepository.SendEventAsync(listEvents);
return Ok("Drone Completed");
}
case Operations.ChoreographyOperation.GetDrone:
{
var deliverySchedule = await deliveryServiceCaller.ScheduleDeliveryAsync(delivery, e.Subject);
return Ok("Delivery Completed");
}
return BadRequest();
}
}
関連リソース
コレオグラフィの設計では、これらのパターンを検討してください。
アンバサダー設計パターンを使用してビジネス サービスをモジュール化します。
ワークロードの急増に対処するために、キュー ベースの負荷平準化パターンを実装します。
パブリッシャーとサブスクライバーのパターンを通じて、非同期の分散型メッセージングを使用します。
補償トランザクションを使用して、1 つ以上の関連する操作が失敗した場合に、一連の成功した操作を元に戻します。
メッセージング インフラストラクチャでのメッセージ ブローカーの使用については、「Azure での非同期メッセージングのオプション」を参照してください。