The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

Narayana LRA 参加者サポート

はじめに

LRA(Long Running Actionの略)参加者エクステンションは、マイクロサービスベースの設計で、異なるサービスが分散一貫性の緩和された概念を利用できる場合に便利です。

The idea is for multiple services to perform different computations/actions in concert, whilst retaining the option to compensate for any actions performed during the computation. This kind of loose coupling of services bridges the gap between strong consistency models such as JTA/XA and "home-grown" ad hoc consistency solutions.

The model is based on the Eclipse MicroProfile LRA specification. The approach is for the developer to annotate a business method with a Java annotation (@LRA). When such a method is called, an LRA context is created (if one is not already present) which is passed along with subsequent JAX-RS invocations until a method is reached which also contains an @LRA annotation with an attribute that indicates that the LRA should be closed or cancelled. The default is for the LRA to be closed in the same method that started the LRA (which itself may have propagated the context during method execution). The JAX-RS resource indicates that it wishes to participate in the interaction by, minimally, marking one of the methods with an @Compensate annotation. If the context is later cancelled, then this @Compensate action is guaranteed to be called even in the presence of failures and is the trigger for the resource to compensate for any activities it performed in the context of the LRA. This guarantee enables services to operate reliably with the assurance of eventual consistency (when all compensation activities have ran to completion). The participant can ask to be reliably notified when the LRA it is participating in is closed by marking one of the methods with an @Complete annotation. In this way cancelling an LRA causes all participants to be notified via their Compensate callback and closing an LRA causes all participants to be notified via their Complete callback (if they have one). Other annotations for controlling participants are documented in the MicroProfile LRA API v1.0 javadoc.

設定

Quarkus Mavenプロジェクトの設定が完了したら、プロジェクトのベースディレクトリで以下のコマンドを実行して、 narayana-lra エクステンションを追加できます。

CLI
quarkus extension add 'narayana-lra,resteasy-jackson,rest-client-jackson'
Maven
./mvnw quarkus:add-extension -Dextensions="narayana-lra,resteasy-jackson,rest-client-jackson"
Gradle
./gradlew addExtension --extensions="narayana-lra,resteasy-jackson,rest-client-jackson"

これにより、pom.xmlに以下が追加されます。

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-narayana-lra</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-client-jackson</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-narayana-lra")
implementation("io.quarkus:quarkus-resteasy-jackson")
implementation("io.quarkus:quarkus-rest-client-jackson")

quarkus-narayana-lra が動作するためには、サーバーの JAX-RS 実装と REST クライアントの実装で補完する必要があります。つまり、ユーザーのアプリケーションには、 quarkus-resteasy-jacksonquarkus-rest-client または quarkus-resteasy-reactive-jacksonquarkus-rest-client-reactive のいずれかの依存関係が必要です。

コーディネーターがいる場合は、これだけで新しいLRAを作り、参加者を募ることができます。

LRAエクステンションは、 src/main/resources ディレクトリ内の application.properties ファイルを更新することで設定できます。LRA固有のプロパティは、 quarkus.lra.coordinator-url=<url> 、外部コーディネーターのHTTPエンドポイントを指定するだけです。

quarkus.lra.coordinator-url=http://localhost:8080/lra-coordinator

For a Narayana coordinator the path component of the url is normally lra-coordinator. Coordinators can be obtained from https://hub.docker.com/r/jbosstm/lra-coordinator or you can build your own coordinator using a maven pom that includes the appropriate dependencies. A Quarkus quickstart will be provided to show how to do this, or you can take a look at one of the Narayana quickstarts. Another option would be to run it managed inside a WildFly application server.

失敗の処理

When an LRA is told to finish, i.e. when a method annotated with @LRA(end = true, …​) is invoked, the coordinator will instruct all services involved in the interaction to finish. If a service is unavailable (or still finishing) then the coordinator will retry periodically. It is the user’s responsibility to restart failed services on the same endpoint that they used when they first joined the LRA, or to tell the coordinator that they wish to be notified on new endpoints. An LRA is not deemed finished until all participants have acknowledged that they have finished.

The coordinator is responsible for reliably creating and ending LRAs and for managing participant enlistment, and it therefore must be available (for example if it or the network fail then something in the environment is responsible for restarting the coordinator or for repairing the network, respectively). To fulfill this task the coordinator must have access to durable storage for its logs (via a filesystem or in a database). At the time of writing, managing coordinators is the responsibility of the user. An "out-of-the-box" solution will be forthcoming.

以下は、LRAを開始する方法と、後にLRAがキャンセルされたり( @Compensate のアノテーション付きメソッドが呼び出される)、クローズされたり( @Complete が呼び出される)したときに通知を受け取る方法の簡単な例です。

@Path("/")
@ApplicationScoped
public class SimpleLRAParticipant
{
    @LRA(LRA.Type.REQUIRES_NEW) // a new LRA is created on method entry
    @Path("/work")
    @PUT
    public void doInNewLongRunningAction(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId)
    {
        /*
         * Perform business actions in the context of the LRA identified by the
         * value in the injected JAX-RS header. This LRA was started just before
         * the method was entered (REQUIRES_NEW) and will be closed when the
         * method finishes at which point the completeWork method below will be
         * invoked.
         */
    }

    @org.eclipse.microprofile.lra.annotation.Complete
    @Path("/complete")
    @PUT
    public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
                                 String userData)
    {
        /*
         * Free up resources allocated in the context of the LRA identified by the
         * value in the injected JAX-RS header.
         *
         * Since there is no @Status method in this class, completeWork MUST be
         * idempotent and MUST return the status.
         */
         return Response.ok(ParticipantStatus.Completed.name()).build();
    }

    @org.eclipse.microprofile.lra.annotation.Compensate
    @Path("/compensate")
    @PUT
    public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
                                   String userData)
    {
        /*
         * The LRA identified by the value in the injected JAX-RS header was
         * cancelled so the business logic should compensate for any actions
         * that have been performed while running in its context.
         *
         * Since there is no @Status method in this class, compensateWork MUST be
         * idempotent and MUST return the status
         */
         return Response.ok(ParticipantStatus.Compensated.name()).build();
    }
}

また、この例では、LRAが存在する場合、 @HeaderParam JAX-RSアノテーションタイプを介してリクエストヘッダを読み取ることで、その識別子を取得できることを示しています。

ここでは、 LRA アノテーションの end 要素を使用して、あるリソース・メソッドで LRA を開始し、別のリソース・メソッドで閉じる方法の例を示しています。また、ビジネスメソッドが cancelOn および cancelOnFamily 要素で特定される特定の HTTP ステータスコードを返した場合に、LRA が自動的にキャンセルされるように設定する方法も示しています。

  @LRA(value = LRA.Type.REQUIRED, // if there is no incoming context a new one is created
       cancelOn = {
           Response.Status.INTERNAL_SERVER_ERROR // cancel on a 500 code
       },
       cancelOnFamily = {
           Response.Status.Family.CLIENT_ERROR // cancel on any 4xx code
       },
       end = false) // the LRA will continue to run when the method finishes
  @Path("/book")
  @POST
  public Response bookTrip(...) { ... }

  @LRA(value = LRA.Type.MANDATORY, // requires an active context before method can be executed
       end = true) // end the LRA started by the bookTrip method
  @Path("/confirm")
  @PUT
  public Booking confirmTrip(Booking booking) throws BookingException { ... }

The end = false element on the bookTrip method forces the LRA to continue running when the method finishes and the end = true element on the confirmTrip method forces the LRA (started by the bookTrip method) to be closed when the method finishes. Note that this end element can be placed on any JAX-RS resource (ie one service can start the LRA whilst a different service ends it). There are many more examples in the Microprofile LRA specification document and in the Microprofile LRA TCK.