Javaにおけるコマンドパターン:複雑なコマンドを容易にオーケストレーション
別名
- 分散トランザクションコマンド
- トランザクションコーディネーター
コマンドデザインパターンの意図
Javaにおけるコマンドパターンの意図、特に分散トランザクションのコンテキストでは、複数の分散コンポーネントまたはサービスにわたる複雑なトランザクションを管理および調整することです。このパターンは、分散システムにおけるデータの整合性と一貫性を確保し、マイクロサービスアーキテクチャにとって重要です。トランザクションコマンドと調整ロジックをカプセル化し、2フェーズコミットやSagaなどの分散トランザクションプロトコルの実装を容易にします。
実際の例を用いたコマンドパターンの詳細な説明
実世界の例
世界中から様々なバンドが出演する大規模な国際音楽フェスティバルの開催を考えてみましょう。各バンドの到着、サウンドチェック、パフォーマンスは、分散システムにおける個々のトランザクションに似ています。「コマンド」が分散トランザクションを調整して全体的な整合性と信頼性を維持するJavaのコマンドパターンを反映しています。フェスティバル主催者は「コマンド」として機能し、これらのトランザクションを調整して、バンドのフライトが遅延した場合(トランザクションの失敗に類似)、全体のスケジュールを維持するために、スケジュール変更や他のバンドとの時間枠の入れ替え(補償アクション)などのバックアッププランがあることを保証します。この設定は、分散トランザクションにおけるコマンドパターンを反映しており、個々の障害にもかかわらず成功した結果を達成するために、さまざまなコンポーネントを調整する必要があります。
分かりやすく言うと
コマンドパターンは、リクエストをスタンドアロンオブジェクトに変換し、コマンドのパラメーター化、アクションのキューイング、および元に戻す操作の実装を可能にします。
Javaにおけるコマンドパターンのプログラム例
`Payment`と`Shipping`のマイクロサービスが分離されたeコマースプラットフォームなど、分散システムの異なるサービス間でトランザクションを管理するには、慎重な調整が必要です。トランザクション調整にJavaのコマンドパターンを使用すると、サービスで部分的な障害が発生した場合でも、データの整合性と信頼性を確保できます。
これに対処するための戦略の1つは、プロセスを調整する`Commander`コンポーネントを使用することです。最初に、注文は利用可能なサービス(この場合は`Shipping`)によって処理されます。`Commander`は、注文の詳細をデータベースに保存するか、将来の処理のためにキューイングすることにより、現在利用できないサービス(`Payment`)と注文を同期しようとします。このキューイングシステムは、キューにリクエストを追加する際の潜在的な障害も考慮する必要があります。
`Commander`は、両方のサービスが最終的に同じトランザクションデータを反映するように、キューに入れられた注文を繰り返し処理しようとします。このプロセスには、冪等性を確保することが含まれます。つまり、同じ注文同期リクエストが複数回行われた場合でも、一度だけ実行され、重複トランザクションを防ぎます。目標は、初期の障害や遅延にもかかわらず、すべてのシステムが時間の経過とともに同期される、サービス間の最終的な整合性を達成することです。
`AppAllCases`クラスで`Commander`クラスがどのように使用されるかの簡略例を次に示します
public class AppAllCases {
// ... other methods ...
// Shipping Database Fail Cases
void itemUnavailableCase() {
var ps = new PaymentService(new PaymentDatabase());
var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException());
var ms = new MessagingService(new MessagingDatabase());
var eh = new EmployeeHandle(new EmployeeDatabase());
var qdb = new QueueDatabase();
// Create a Commander instance
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
var order = new Order(user, "book", 10f);
// Use the Commander instance to place an order
c.placeOrder(order);
}
// ... other methods ...
}
`itemUnavailableCase`メソッドでは、それぞれのサービスとそのデータベースを使用して`Commander`インスタンスが作成されます。次に、`User`と`Order`が作成され、`Commander`インスタンスの`placeOrder`メソッドが注文とともに呼び出されます。これにより、注文を行い、コマンドパターンに従って障害を処理するプロセスがトリガーされます。
`Commander`クラスは、注文の配置と潜在的な障害の処理のためのロジックをカプセル化します。この懸念事項の分離により、コードの理解と保守が容易になり、アプリケーションのさまざまな部分で`Commander`クラスを再利用できます。実際のアプリケーションでは、`Commander`クラスはより複雑になり、さまざまなタイプの障害の処理、失敗した操作の再試行、および複数のサービスにわたるトランザクションの調整のための追加ロジックが含まれます。
`itemUnavailableCase`を実行した結果の出力がこちらです
09:10:13.894 [main] DEBUG com.iluwatar.commander.Commander -- Order YN3V8B7IL2PI: Error in creating shipping request..
09:10:13.896 [main] INFO com.iluwatar.commander.Commander -- This item is currently unavailable. We will inform you as soon as the item becomes available again.
09:10:13.896 [main] INFO com.iluwatar.commander.Commander -- Order YN3V8B7IL2PI: Item book unavailable, trying to add problem to employee handle..
09:10:13.897 [Thread-0] INFO com.iluwatar.commander.Commander -- Order YN3V8B7IL2PI: Added order to employee database
Javaでコマンドパターンを使用する場合
以下の場合に、分散トランザクションにJavaのコマンドパターンを使用します
- 部分的なシステム障害が発生した場合に、分散サービス全体のデータ整合性を確保する必要がある場合。
- トランザクションが、調整されたコミットまたはロールバックを必要とする複数のマイクロサービスまたは分散コンポーネントにまたがる場合。
- ロールバックのための補償アクションを必要とする長時間実行されるトランザクションを実装している場合。
Javaにおけるコマンドパターンの実際のアプリケーション
- 2フェーズコミット(2PC)プロトコル:分散データベースまたはサービス全体のコミットまたはロールバックの調整。
- Sagaパターン実装:複数のマイクロサービスにまたがる長時間実行されるビジネスプロセスを管理し、各ステップにロールバックのための補償アクションがあります。
- マイクロサービスアーキテクチャにおける分散トランザクション:データの整合性と一貫性を維持しながら、マイクロサービス全体の複雑な操作を調整します。
コマンドパターンの利点とトレードオフ
利点
- 複雑な分散トランザクションを管理するための明確なメカニズムを提供し、システムの信頼性を向上させます。
- 長時間実行されるトランザクションで一貫性を維持するために重要な補償トランザクションの実装を可能にします。
- トランザクションコンテキスト内での異種システムの統合を容易にします。
トレードオフ
- 調整されたロールバックメカニズムの必要性のために、特に障害シナリオでは、複雑さが増します。
- 調整と整合性チェックのオーバーヘッドにより、パフォーマンスに影響を与える可能性があります。
- Sagaベースの実装は、ビジネスプロセスフロー全体を理解する際の複雑さを増す可能性があります。
関連するJavaデザインパターン
- Sagaパターン:分散トランザクションのコマンドパターンと並行してよく議論され、補償アクションを伴う長時間実行されるトランザクションに焦点を当てています。