マイクロサービスアーキテクチャの隠れたヒーローである分散サガについてお話ししましょう。モノリスが過去のものとなった今、複数のサービス間でのトランザクション管理は本当に頭痛の種です。そこで登場するのが分散サガです。このパターンは、2フェーズコミットプロトコルを必要とせずに、サービス間のデータ整合性を維持するのに役立ちます。

これは、各サービスが自分のステップを知っていて、誰かがつまずいたときに優雅に回復する方法を知っている振り付けされたダンスのようなものです。それは、各自が自分のボールを空中に保つ責任を持つ熟練したジャグラーのチームを持っているようなものであり、仲間のジャグラーがボールを落としたときに助ける方法も知っています。

QuarkusとMicroProfile LRAの登場

さて、「なぜQuarkusとMicroProfile LRAなのか?」と思うかもしれません。それは、スポーツカーを選ぶ理由を聞くようなものです。Quarkusは超音速のサブアトミックJavaフレームワークであり、MicroProfile LRAと組み合わせることで、分散サガを簡単に実装する力を与えてくれます。(まあ、「Hello, World!」プログラムを書くほど簡単ではないかもしれませんが、言いたいことはわかるでしょう。)

Quarkus: スピードデーモン

Quarkusは次のような利点をもたらします:

  • 驚異的な起動時間の速さ
  • 低メモリ使用量
  • 開発者の喜び(そう、それも機能です!)

MicroProfile LRA: オーケストレーター

MicroProfile LRAは次のことを提供します:

  • 長時間実行されるアクションを定義し管理するための標準化された方法
  • 自動補償処理
  • 既存のJava EEおよびMicroProfileアプリケーションとの簡単な統合

実践してみよう: 分散サガの実装

理論はここまで!実際の例に飛び込みましょう。3つのサービス(注文、支払い、在庫)を含むシンプルなeコマースサガを実装します。

ステップ1: プロジェクトのセットアップ

まず、必要な拡張機能を持つ新しいQuarkusプロジェクトを作成しましょう:

mvn io.quarkus:quarkus-maven-plugin:create \
    -DprojectGroupId=com.example \
    -DprojectArtifactId=saga-demo \
    -DclassName="com.example.OrderResource" \
    -Dpath="/order" \
    -Dextensions="resteasy-jackson,microprofile-lra"

ステップ2: 注文サービスの実装

注文サービスから始めましょう。@LRAアノテーションを使用して、メソッドを長時間実行されるアクションの一部としてマークします:

@Path("/order")
public class OrderResource {

    @POST
    @LRA(LRA.Type.REQUIRES_NEW)
    @Path("/create")
    public Response createOrder(Order order) {
        // 注文を作成するロジック
        return Response.ok(order).build();
    }

    @PUT
    @Path("/compensate")
    @Compensate
    public Response compensateOrder(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
        // 注文を補償(キャンセル)するロジック
        return Response.ok().build();
    }
}

ステップ3: 支払いサービスの実装

次に、支払いサービスを実装しましょう:

@Path("/payment")
public class PaymentResource {

    @POST
    @LRA(LRA.Type.MANDATORY)
    @Path("/process")
    public Response processPayment(Payment payment) {
        // 支払いを処理するロジック
        return Response.ok(payment).build();
    }

    @PUT
    @Path("/compensate")
    @Compensate
    public Response compensatePayment(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
        // 支払いを返金するロジック
        return Response.ok().build();
    }
}

ステップ4: 在庫サービスの実装

最後に、在庫サービスを実装しましょう:

@Path("/inventory")
public class InventoryResource {

    @POST
    @LRA(LRA.Type.MANDATORY)
    @Path("/reserve")
    public Response reserveInventory(InventoryRequest request) {
        // 在庫を予約するロジック
        return Response.ok(request).build();
    }

    @PUT
    @Path("/compensate")
    @Compensate
    public Response compensateInventory(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
        // 予約された在庫を解放するロジック
        return Response.ok().build();
    }
}

すべてをまとめる

サービスを実装したので、サガでどのように動作するか見てみましょう:

@Path("/saga")
public class SagaResource {

    @Inject
    OrderResource orderResource;

    @Inject
    PaymentResource paymentResource;

    @Inject
    InventoryResource inventoryResource;

    @POST
    @Path("/execute")
    @LRA(LRA.Type.REQUIRES_NEW)
    public Response executeSaga(SagaRequest request) {
        Response orderResponse = orderResource.createOrder(request.getOrder());
        Response paymentResponse = paymentResource.processPayment(request.getPayment());
        Response inventoryResponse = inventoryResource.reserveInventory(request.getInventoryRequest());

        // レスポンスをチェックし、コミットまたは補償を決定
        if (orderResponse.getStatus() == 200 && 
            paymentResponse.getStatus() == 200 && 
            inventoryResponse.getStatus() == 200) {
            return Response.ok("サガが正常に完了しました").build();
        } else {
            // どのステップが失敗しても、LRAコーディネーターが自動的に
            // 参加サービスの@Compensateメソッドを呼び出します
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                           .entity("サガが失敗し、補償がトリガーされました")
                           .build();
        }
    }
}

プロットが厚くなる: 失敗シナリオの処理

さて、問題が発生したときにどうなるかについて話しましょう。MicroProfile LRAがあなたをサポートします!サガのどのステップが失敗しても、LRAコーディネーターが自動的にすべての参加サービスの補償メソッドをトリガーします。

例えば、注文が作成され、在庫が予約された後に支払いが失敗した場合:

  1. 支払いサービスのcompensatePaymentメソッドが呼び出されます(この場合、何もする必要がないかもしれません)。
  2. 在庫サービスのcompensateInventoryメソッドが呼び出され、予約された在庫が解放されます。
  3. 注文サービスのcompensateOrderメソッドが呼び出され、注文がキャンセルされます。

これにより、システムは失敗が発生しても一貫した状態を保ちます。それは、ワイルドなパーティーの後に掃除をする熟練した清掃員のチームを持っているようなもので、どんなに混沌としていても、彼らはそれをコントロールしています。

学んだ教訓とベストプラクティス

QuarkusとMicroProfile LRAを使用した分散サガの世界への旅を締めくくるにあたり、いくつかの重要なポイントを振り返りましょう:

  • 冪等性が鍵です: サービスの操作と補償が冪等であることを確認してください。これは、初回の適用以降、結果を変更せずに複数回呼び出すことができることを意味します。
  • シンプルに保つ: サガは強力ですが、すぐに複雑になる可能性があります。サガのステップ数を最小限に抑え、失敗の可能性を減らしましょう。
  • 広範な監視とログを実装する: サガのデバッグに役立つ詳細なログと監視を実装しましょう。
  • 最終的な整合性を考慮する: サガは即時の整合性ではなく、最終的な整合性を提供します。これを念頭に置いてシステムとユーザーエクスペリエンスを設計しましょう。
  • テスト、テスト、そして再テスト: サガの包括的なテストを実施し、失敗シナリオも含めましょう。Quarkus Dev Servicesのようなツールは、現実的な環境でのテストに非常に役立ちます。

結論: 混沌を受け入れる

QuarkusとMicroProfile LRAを使用した分散サガの実装は、単にコードを書くことではなく、分散システムの混沌とした性質を優雅に、そしてレジリエンスを持って制御することです。それは、マイクロサービスのサーカスでライオン使いになるようなもので、エキサイティングで少し危険ですが、最終的にはやりがいがあります。

マイクロサービスの旅に出る際には、分散サガのようなパターンが、堅牢でスケーラブルなシステムを目指す冒険の頼れる仲間であることを忘れないでください。すべての問題を解決するわけではありませんが、確実に生活を楽にしてくれます。

さあ、勇敢な開発者よ、サガが常にあなたの味方でありますように!そして、迷ったときは、補償、補償、補償を忘れずに!

「マイクロサービスの世界では、うまく実装されたサガは千の2フェーズコミットに値する。」 - 賢い開発者(おそらく)

ハッピーコーディング!トランザクションが常に一貫していることを願っています!