Tuesday, August 17, 2021

LRA annotation checker Maven plugin

With the release of the LRA (Long Running Actions) specification in version 1.0 Narayana team works on integrating the Narayana implementation to various application runtimes. Currently it's Quarkus and WildFly. (Camel is a third platform integrating the Narayana implementation but it does in a way not depending on LRA annotations defined in the specification.).

NOTE: If you want to get introduction what is LRA and what is good for you can read some of the already published articles (1, 2).

At this time when the LRA  can be easily grab and used within the application runtimes it may come some difficulty on precise use of the LRA annotations.

The specification defines different "requirements" the LRA application has to follow to work correctly. Some are basic as "the LRA annotated class must contain at least one of the methods annotated with @Compensate or @AfterLRA" or that the LRA annotated JAX-RS endpoints has predefined HTTP methods to be declared with. For example the @Complete/@Compensate requires the @PUT method while the @Forgot requires the @DELETE and @Status requires the @GET.

When the specific LRA contract rule is violated the developer will find them at the deployment time with the RuntimeException being thrown. But time of the deployment could be a bit late to find just a forgotten annotation required by the LRA specification. With that idea in mind Narayana offers a Maven plugin project Narayana LRA annotation checker.

The developer working with the LRA introduces the dependency to the LRA annotation with artifact org.eclipse.microprofile.lra:microprofile-lra-api:1.0. He codes the application and then he can introduce the Maven plugin of the LRA checker to be run during Maven phase process-classes. The developer needs to point to the plugin goal check to get the verification being run.

The snippets that can be placed to the project pom.xml is following. The plugin Maven artifact coordinates is io.narayana:maven-plugin-lra-annotations_1.0:1.0.0.Beta1

...
<build>
  <plugins>
    <plugin>
      <groupId>io.narayana</groupId>
      <artifactId>maven-plugin-lra-annotations_1.0</artifactId>
      <version>1.0.0.Beta1</version>
      <executions>
        <execution>
          <goals>
            <goal>check</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
...

When plugin is loaded it searches for classes available at path ${project.build.directory}/classes (i.e., target/classes) and tries to find if the application preserve the rules defined by the LRA specification. When not then the Maven build fails reporting what error happens.
Such an error is in format of [error id]:[description]. Example of such error is

[ERROR] Failed to execute goal io.narayana:maven-plugin-lra-annotations_1.0:1.0.0.Beta1:check (default) on project lra-annotation-checker-maven-plugin-test: LRA annotation errors:
[ERROR] [[4]]->
  1: The class annotated with org.eclipse.microprofile.lra.annotation.ws.rs.LRA missing at least one of the annotations Compensate or AfterLRA Class: io.narayana.LRAParticipantResource;
  2: Multiple annotations of the same type are used. Only one per the class is expected. Multiple annotations 'org.eclipse.microprofile.lra.annotation.Status' in the class 'class io.narayana.LRAParticipantResource' on methods [status, status2].;
  4: Wrong method signature for non JAX-RS resource method. Signature for annotation 'org.eclipse.microprofile.lra.annotation.Forget' in the class 'io.narayana.LRAParticipantResource' on method 'forget'. It should be 'public void/CompletionStage/ParticipantStatus forget(java.net.URI lraId, java.net.URI parentId)';
  5: Wrong complementary annotation of JAX-RS resource method. Method 'complete' of class 'class io.narayana.LRAParticipantResource' annotated with 'org.eclipse.microprofile.lra.annotation.Complete' misses complementary annotation javax.ws.rs.PUT.

The plugin can be configured with two parameters (placed under <configuration> under the <plugin> element).

Attribute Description Default
paths Paths searched for classes to be checked. Point to a directory or jar file. Multiple paths are delimited with a comma. ${project.build.directory}/classes
failWhenPathNotExist When some path defined within argument paths does not exist then Maven build may fail or resume with information that the path is not available. true

All the points described in this article can be seen and tested in an example at github.com/jbosstm/artifacts#jbossts.blogspot/17-08-2021-lra-annotation-checker. The plugin configuration can be seen in the pom.xml of the same project.

Any comments, ideas for enhancement and bug reports are welcomed. The project LRA annotation checker Maven plugin is placed in the jbosstm incubator repository. The issues can be submitted via JBTM issue tracker at https://issues.jboss.org/browse/JBTM.

Hopefully this small plugin provides a better experience for any developer working with the LRA and LRA Narayana implementation in particular.

Tuesday, July 20, 2021

How to use Long Running Actions between microservices

Introduction

In my last post I showed how to run a Long Running Action (LRA) within a single JAX-RS resource method using quarkus features to build and run the application. I showed how to create and start an LRA coordinator and then generated a basic hello application, showing how to modify the application to run with a long running action (by adding dependencies on the org.eclipse.microprofile.lra:microprofile-lra-api and org.jboss.narayana.rts:narayana-lra artifacts, which together provide annotations for controlling the lifecycle of LRAs). That post also includes links to the the LRA specification and to the javadoc for the annotation API.

In this follow up post I will indicate how to include a second resource in the LRA. To keep things interesting I’ll deploy the second resource to another microservice and use quarkus’s MicroProfile Rest Client support to implement the remote service invocations. The main difference between this example and the one I developed in the earlier post, apart from the technicalities of using Rest Client, is that we will set the LRA.end attribute to false in the remote service so that the LRA will remain active when the call returns. In this way the initiating service method has the option of calling other microservices before ending the LRA.

Creating and starting an LRA coordinator

LRA relies on a coordinator to manage the lifecycle of LRAs so you will need one to be running for this demo to work successfully. The previous post showed how to build and run coordinators. Alternatively, download or view some scripts which execute all of the steps required in the current post and it includes a shell script called coordinator.sh which will build a runnable coordinator jar (it’s fairly simple and short so you can just read it and create your own jar or just run it as is).

Generate a project for booking tickets

Since the example will be REST based, include the resteasy and rest-client extensions (on line 6 next):

    1: mvn io.quarkus:quarkus-maven-plugin:2.0.1.Final:create \
    2:     -DprojectGroupId=org.acme \
    3:     -DprojectArtifactId=ticket \
    4:     -DclassName="org.acme.ticket.TicketResource" \
    5:     -Dpath="/tickets" \
    6:     -Dextensions="resteasy,rest-client"
    7: cd ticket

You will need the mvn program to run the plugin (but the generated projects will include the mvnw maven wrapper).

Modify the generated TicketResource.java source file to add Microprofile LRA support. The changes that you will need for LRA are on lines 26 and 27. Line 26 says that the bookTicket method must run with an LRA (if one is not present when the method is invoked then one will be automatically created). Note that we have set the end attribute to false to stop the LRA from being automatically closed when the method finishes. By keeping the LRA active when the ticket is booked, the caller can invoke other services in the context of the same LRA. Most services will require the LRA context for tracking updates which typically will be useful for knowing which actions to compensate for if the LRA is later cancelled: the context is injected as a JAX-RS method parameter on line 27.

You will also need to include callbacks for when the LRA is later closed or cancelled (the methods are defined on lines 37 and line 46, respectively).

    1: package org.acme.ticket;
    2:
    3: import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
    4:
    5: // import annotation definitions
    6: import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
    7: import org.eclipse.microprofile.lra.annotation.Compensate;
    8: import org.eclipse.microprofile.lra.annotation.Complete;
    9: // import the definition of the LRA context header
   10: import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER;
   11:
   12: // import some JAX-RS types
   13: import javax.ws.rs.GET;
   14: import javax.ws.rs.PUT;
   15: import javax.ws.rs.Path;
   16: import javax.ws.rs.Produces;
   17: import javax.ws.rs.core.Response;
   18: import javax.ws.rs.HeaderParam;
   19:
   20: @Path("/tickets")
   21: @Produces(APPLICATION_JSON)
   22: public class TicketResource {
   23:
   24:     @GET
   25:     @Path("/book")
   26:     @LRA(value = LRA.Type.REQUIRED, end = false) // an LRA will be started before method execution if none exists and will not be ended after method execution
   27:     public Response bookTicket(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
   28:         System.out.printf("TicketResource.bookTicket: %s%n", lraId);
   29:         String ticket = "1234"
   30:         return Response.ok(ticket).build();
   31:     }
   32:
   33:     // ask to be notified if the LRA closes:
   34:     @PUT // must be PUT
   35:     @Path("/complete")
   36:     @Complete
   37:     public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
   38:         System.out.printf("TicketResource.completeWork: %s%n", lraId);
   39:         return Response.ok().build();
   40:     }
   41:
   42:     // ask to be notified if the LRA cancels:
   43:     @PUT // must be PUT
   44:     @Path("/compensate")
   45:     @Compensate
   46:     public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
   47:         System.out.printf("TicketResource.compensateWork: %s%n", lraId);
   48:         return Response.ok().build();
   49:     }
   50: }

Skip the tests:

rm src/test/java/org/acme/ticket/*

Add dependencies on microprofile-lra-api and narayana-lra to the pom to include the MicroProfile LRA annotations and the narayana implementation of them so that the LRA context will be propagated during interservice communications:

    <dependencies>
      <dependency>
        <groupId>org.eclipse.microprofile.lra</groupId>
        <artifactId>microprofile-lra-api</artifactId>
        <version>1.0</version>
      </dependency>
      <dependency>
        <groupId>org.jboss.narayana.rts</groupId>
        <artifactId>narayana-lra<\/artifactId>
        <version>5.12.0.Final</version>
      </dependency>

We are creating ticket and trip microservices so they need to listen on different ports, configure the ticket service to run on port 8081:

    1: quarkus.arc.exclude-types=io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipantRegistry,io.narayana.lra.filter.ServerLRAFilter,io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipantResource
    2: quarkus.http.port=8081
    3: quarkus.http.test-port=8081

The excludes are pulled in by the org.jboss.narayana.rts:narayana-lra maven dependency. As mentioned in my previous post this step will not be necessary when the pull request for the io.quarkus:quarkus-narayana-lra extension is approved. Now build and test the ticket service, making sure that you have already started a coordinator as described in the previous blog (or you can use the shell scripts linked above):

./mvnw clean package -DskipTests # skip tests
java -jar target/quarkus-app/quarkus-run.jar & # run the application in the background
curl http://localhost:8081/tickets/book
TicketResource.bookTicket: http://localhost:8080/lra-coordinator/0_ffffc0a8000e_8b2b_60f6a8d4_2
1234

The bookTicket() method prints the method name and the id of the active LRA followed by the hard-coded booking id 1234.

Generate a project for booking trips

Now create a second microservice which will be used for booking trips. It will invoke other microservices to complete trip bookings. In order to simplify the example there is just the single remote ticket service involved in the booking process.

First generate the project. Like the ticket service, the example will be REST based so include the resteasy and rest-client extensions:

mvn io.quarkus:quarkus-maven-plugin:2.0.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=trip \
    -DclassName="org.acme.trip.TripResource" \
    -Dpath="/trips" \
    -Dextensions="resteasy,rest-client"

cd trip

The rest-client extension includes support for MicroProfile REST Client which we shall use to perform the remote REST invocations from the trip to the ticket service. For REST Client we need a TicketService and we need to register it as shown on line 12 of the following listing:

    1: package org.acme.trip;
    2:
    3: import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
    4:
    5: import javax.ws.rs.GET;
    6: import javax.ws.rs.Path;
    7: import javax.ws.rs.Produces;
    8: import javax.ws.rs.core.MediaType;
    9:
   10: @Path("/tickets")
   11: @Produces(MediaType.APPLICATION_JSON)
   12: @RegisterRestClient
   13: public interface TicketService {
   14:
   15:     @GET
   16:     @Path("/book")
   17:     String bookTicket();
   18: }

Let’s also create a TripService and inject an instance of the TicketService into it, marking it with the @RestClient annotation on line 11. The quarkus rest client support will configure this injected instance such that it will perform remote REST calls to the ticket service (the remote endpoint for the ticket service will be configured below in the application.properties file):

    1: package org.acme.trip;
    2:
    3: import org.eclipse.microprofile.rest.client.inject.RestClient;
    4: import javax.enterprise.context.ApplicationScoped;
    5: import javax.inject.Inject;
    6:
    7: @ApplicationScoped
    8: public class TripService {
    9:
   10:     @Inject
   11:     @RestClient
   12:     TicketService ticketService;
   13:
   14:     String bookTrip() {
   15:         return ticketService.bookTicket(); // only one service will be used for the trip booking
   16:
   17:         // if other services need to be part of the trip they would be called here
   18:         // and the TripService would associate each step of the booking with the id of the LRA
   19:         // (although I've not shown it being passed in this example) and that would form the
   20:         // basis of the ability to compensate or clean up depending upon the outcome.
   21:         // We may include a more comprehensive/realistic example in a later blog.
   22:     }
   23: }

And now we can inject an instance of this service into the generated TripResource (src/main/java/org/acme/trip/TripResource.java) on line 26. I have also annotated the bookTrip() method with an LRA annotation so that a new LRA will be started before the method is started (if one wasn’t already present) and I have added @Complete and @Compensate callback methods (these will be called when the LRA closes or cancels, respectively):

    1: package org.acme.trip;
    2:
    3: import javax.inject.Inject;
    4: import javax.ws.rs.GET;
    5: import javax.ws.rs.Path;
    6: import javax.ws.rs.Produces;
    7: import javax.ws.rs.core.Response;
    8:
    9: import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
   10:
   11: // import annotation definitions
   12: import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
   13: import org.eclipse.microprofile.lra.annotation.Compensate;
   14: import org.eclipse.microprofile.lra.annotation.Complete;
   15: // import the definition of the LRA context header
   16: import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER;
   17:
   18: // import some JAX-RS types
   19: import javax.ws.rs.PUT;
   20: import javax.ws.rs.HeaderParam;
   21:
   22: @Path("/trips")
   23: @Produces(APPLICATION_JSON)
   24: public class TripResource {
   25:
   26:     @Inject
   27:     TripService service;
   28:
   29:     // annotate the hello method so that it will run in an LRA:
   30:     @GET
   31:     @LRA(LRA.Type.REQUIRED) // an LRA will be started before method execution and ended after method execution
   32:     @Path("/book")
   33:     public Response bookTrip(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
   34:         System.out.printf("TripResource.bookTrip: %s%n", lraId);
   35:         String ticket = service.bookTrip();
   36:         return Response.ok(ticket).build();
   37:     }
   38:
   39:     // ask to be notified if the LRA closes:
   40:     @PUT // must be PUT
   41:     @Path("/complete")
   42:     @Complete
   43:     public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
   44:         System.out.printf("TripResource.completeWork: %s%n", lraId);
   45:         return Response.ok().build();
   46:     }
   47:
   48:     // ask to be notified if the LRA cancels:
   49:     @PUT // must be PUT
   50:     @Path("/compensate")
   51:     @Compensate
   52:     public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
   53:         System.out.printf("TripResource.compensateWork: %s%n", lraId);
   54:         return Response.ok().build();
   55:     }
   56: }

For the blog we can skip the tests:

rm src/test/java/org/acme/trip/*

Configure the trip service to listen on port 8082 (line 2). Also configure the remote ticket endpoint as required by the MicroProfile REST Client specification (line 5):

    1: quarkus.arc.exclude-types=io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipantRegistry,io.narayana.lra.filter.ServerLRAFilter,io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipantResource
    2: quarkus.http.port=8082
    3: quarkus.http.test-port=8082
    4:
    5: org.acme.trip.TicketService/mp-rest/url=http://localhost:8081
    6: org.acme.trip.TicketService/mp-rest/scope=javax.inject.Singleton

Add dependencies on microprofile-lra-api and narayana-lra to the pom to include the MicroProfile LRA annotations and the narayana implementation of them so that the application can request that the LRA context be propagated during interservice communications:

      <dependency>
        <groupId>org.eclipse.microprofile.lra</groupId>
        <artifactId>microprofile-lra-api</artifactId>
        <version>1.0</version>
      </dependency>
      <dependency>
        <groupId>org.jboss.narayana.rts</groupId>
        <artifactId>narayana-lra</artifactId>
        <version>5.12.0.Final</version>
      </dependency>

and finally, build and run the microservice:

./mvnw clean package -DskipTests
java -jar target/quarkus-app/quarkus-run.jar &

Use curl to book a trip. The HTTP GET request to the trips/book endpoint is handled by the trip service bookTrip() method and it then invokes the ticket service to book a ticket. When the bookTrip() method finishes the LRA will be closed (since the default value for the LRA.end attribute is true), triggering calls to the service @Complete methods of the two services:

curl http://localhost:8082/trips/book
TripResource.bookTrip: http://localhost:8080/lra-coordinator/0_ffffc0a8000e_8b2b_60f6a8d4_52c
TicketResource.bookTrip: http://localhost:8080/lra-coordinator/0_ffffc0a8000e_8b2b_60f6a8d4_52c
TripResource.completeWork: http://localhost:8080/lra-coordinator/0_ffffc0a8000e_8b2b_60f6a8d4_52c
TicketResource.bookTrip: http://localhost:8080/lra-coordinator/0_ffffc0a8000e_8b2b_60f6a8d4_52c
TicketResource.completeWork: http://localhost:8080/lra-coordinator/0_ffffc0a8000e_8b2b_60f6a8d4_52c
1234

Notice the output shows the bookTrip and bookTicket methods being called and also notice that the @Complete methods of both services (completeWork()) were called. The id of the LRA on all calls should be the same value as shown in the example output, this is worthwhile noting since the completion and compensation methods will typically use it in order to determine which actions it should clean up for or compensate for when the LRA closes or cancels.

Not shown here, but if there was a problem booking the ticket then the ticket service should return a JAX-RS status code (4xx and 5xx HTTP codes by default) that triggers the cancellation of the LRA, and this would then cause the @Compensate methods of all services involved in the LRA to be invoked.


Wednesday, July 14, 2021

Narayana LRA Update

Introduction

This is another in a series of blogs about the compensation based approach to transactions that the team have been producing over the years. The latest such model is LRA (Long Running Actions), originally based on the 2006 OASIS LRA spec, which was recently been accepted by the Eclipse Foundation. The specification and the javadoc for the annotation API are also available.

Although LRA is a simple protocol it has a number of interesting features and one blog won’t do it justice. In this, the first one, I will describe how to create a simple microservice that executes in the context of an LRA. Section 1 explains how to create and run an LRA coordinator, section 2 describes how to create and run a participant, and in the final section there is a short review of the many blogs on the subject that the team have created during the past decade. These blogs are an excellent source of wisdom so I will try to avoid repeating old ground and refer the reader to those blogs for the details of the general approach (of which MP-LRA is just the latest incarnation).

In follow up blogs the team and I plan to cover, in no particular order:

  • how to participate in failure recovery (including participant, coordinator and network failures)

  • writing participants in languages other than Java

  • nesting LRA’s to structure business flows (into hierarchies)

  • various methods of triggering the cancellation of an LRA (resulting in the reliable invocation all compensation activities)

  • leaving LRA’s early

  • inspecting the progress of participants

  • inspecting failed participants (i.e. ones which have finished in a failed state)

  • restarting crashed participants on different endpoints

  • show services interacting with each other in different JVMs

  • show services running on OpenShift

  • leveraging quarkus and WildFly features to simplify the development process (using extensions and galleon feature packs)

  • addressing the demands that cloud infrastructures, such as OpenShift, place on LRA’s

  • investigate some best practices and future plans for managing the availability of coordinators and participants (including participant storage, different storage types such as databases and journals, and scaling of coordinators)

  • strategies for writing compensation logic

  • and I’m sure my colleagues will have plenty of other topics to add to this list.

The Example

A Long Running Action is an interaction between microservices such that all parties (called LRA participants) are guaranteed to be notified when the interaction finishes (in either a successful closing state or an unsuccessful cancelling state). A JAX-RS resource participates in an interaction by marking one or more of its methods with the @LRA annotation and by marking another of its methods with an @Compensate annotation. When a method marked with @LRA is invoked the resource is enlisted in the LRA. Enlisting with an LRA means that if the associated LRA is cancelled then the method annotated with @Compensate is invoked reliably (i.e. it will continue to be called until it is definite that the method executed successfully and that the coordinator received the response). The resource may also request that it be reliably notified if the LRA is closed by marking one of its methods with an @Complete annotation. Note that the LRA id is available to all annotated methods so that all parties know which context is Active.

In order to implement the guarantees stated in the previous paragraph, the narayana implementation requires that there are one or more LRA coordinators running in the system. A coordinator runs on behalf of many services and is responsible for starting and ending LRA’s and for managing the participant membership in the LRA, in other words it must be available for an interaction to progress (start, enlist and end). Similarly, participant resources must be available during the end phase of the LRA so they too must be restarted if they fail.

Note that the developer is normally unconcerned with the coordinator and a typical installation will run them as part of the platform, freeing up the developer to concentrate on the business of creating microservices. However, for the purposes of the blog, first I’ll indicate how you can create one. Note that there is a similar example in our quickstart repo. Later on we will make the blog examples available in the same repo.

Starting a coordinator

Here we show how to build and run a REST based coordinator from scratch as a java executable using the quarkus framework.

The Narayana LRA coordinator is a JAX-RS resource so it needs the quarkus resteasy extension and it needs to depend on the Narayana LRA coordinator implementation.

First generate a quarkus application using the quarkus-maven-plugin, specifying the resteasy-jackson and rest-client extensions which pull in everything we need for JAX-RS:

mvn io.quarkus:quarkus-maven-plugin:2.0.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=narayana-lra-coordinator \
    -Dextensions="resteasy-jackson,rest-client"
cd narayana-lra-coordinator

To obtain coordinator support add the org.jboss.narayana.rts:lra-coordinator-jar:5.12.0.Final maven dependency to the dependencies section of the generated pom.xml file as follows:

    <dependency>
        <groupId>org.jboss.narayana.rts</groupId>
        <artifactId>lra-coordinator-jar</artifactId>
        <version>5.12.0.Final</version>
    </dependency>

Here I have chosen the latest release (5.12.0.Final) of the Narayana LRA coordinator. Because we just need the quarkus framework for running the coordinator, remove the generated example: rm -rf src.

Now build and start the coordinator on port 8080:

rm -rf src
mvn clean package
java -Dquarkus.http.port=8080 -jar target/quarkus-app/quarkus-run.jar &

If you want to check that the coordinator is running try listing the active LRA’s:

curl http://localhost:8080/lra-coordinator

By default the coordinator stores records in the filesystem in a directory called ObjectStore in the user directory (i.e. the value of the system property user.dir). You can change the location by adding a file called src/main/resources/jbossts-properties.xml with content:

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <!-- unique id of an LRA coordinator -->
    <entry key="CoreEnvironmentBean.nodeIdentifier">1</entry>
    <!-- location of the LRA logs -->
    <entry key="ObjectStoreEnvironmentBean.objectStoreDir">target/lra-logs</entry>
    <!-- location of the communications store -->
    <entry key="ObjectStoreEnvironmentBean.communicationStore.objectStoreDir">target/lra-logs</entry>
</properties>

You can test the coordinator is operating correctly by trying to create an LRA using curl, for example.

curl -XPOST http://localhost:8080/lra-coordinator/start
http://localhost:8080/lra-coordinator/0_ffffc0a8000e_9471_60ed85da_a

Note the id of the new LRA in the output.

Now try closing the LRA (include the uid part of the LRA id followed by /close):

curl -XPUT http://localhost:8080/lra-coordinator/0_ffffc0a8000e_9471_60ed85da_a/close
Closed

You may verify that the coordinator no longer has a record of the LRA:

curl http://localhost:8080/lra-coordinator
[]

The output will be a json array ([]) of the LRA’s that the coordinator is managing. Check that the array does not contain the id of the LRA that you have just successfully closed.

Writing and running an LRA participant

We will generate and run a microservice that participates in an LRA using quarkus. A participant should be a JAX-RS resource so we will use the quarkus-maven-plugin, specifying the resteasy-jackson and rest-client extensions (the reason we need rest-client is that the narayana-lra participant support is implemented via a JAX-RS filter which will intercept business requests and needs to invoke the coordinator via JAX-RS calls):

cd ..
mvn io.quarkus:quarkus-maven-plugin:2.0.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=narayana-lra-quickstart \
    -Dextensions="resteasy-jackson,rest-client"
cd narayana-lra-quickstart

There is an outstanding pull request for a narayana-lra quarkus extension (io.quarkus:quarkus-narayana-lra) which includes the necessary support for LRA. Since that isn’t available yet you need to manually do what the extension will do (which, fortunately, is neither difficult nor complex):

Include the following maven dependencies in the generated pom:

    <dependency>
      <groupId>org.eclipse.microprofile.lra</groupId>
      <artifactId>microprofile-lra-api</artifactId>
      <version>1.0</version>
    </dependency>
   <dependency>
      <groupId>org.jboss.narayana.rts</groupId>
      <artifactId>narayana-lra</artifactId>
      <version>5.12.0.Final</version>
    </dependency>

These two dependencies pull in support for the MicroProfile LRA annotations and the Narayana LRA implementation of the behaviour implied by these annotations.

We also need to tell quarkus (via the application.properties config file) to exclude some types from its CDI processing (these types are pulled in by the narayana dependency):

echo "quarkus.arc.exclude-types=io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipantRegistry,io.narayana.lra.filter.ServerLRAFilter,io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipantResource" >> src/main/resources/application.properties

And finally, we just need to update the generated Java JAX-RS resource source code to make use of Long Running Actions (which is the most interesting part for developers):

Open the file src/main/java/org/acme/GreetingResource.java in an editor and annotate the hello method with an @LRA annotation. In addition add two callback methods which will be called when the LRA is closed or cancelled.

// import annotation definitions
import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
import org.eclipse.microprofile.lra.annotation.Compensate;
import org.eclipse.microprofile.lra.annotation.Complete;
// import the definition of the LRA context header
import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER;

// import some JAX-RS types
import javax.ws.rs.PUT;
import javax.ws.rs.core.Response;
import javax.ws.rs.HeaderParam;
...

    // annotate the hello method so that it will run in an LRA:
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @LRA(LRA.Type.REQUIRED) // an LRA will be started before method execution and ended after method execution
    public String hello(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
        return "Hello RESTEasy";
    }

    // ask to be notified if the LRA closes:
    @PUT // must be PUT
    @Path("/complete")
    @Complete
    public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
        return Response.ok().build();
    }

    // ask to be notified if the LRA cancels:
    @PUT // must be PUT
    @Path("/compensate")
    @Compensate
    public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
        return Response.ok().build();
    }

Now build and start the application:

mvn clean package -DskipTests
java -Dquarkus.http.port=8081 -jar target/quarkus-app/quarkus-run.jar &

Ensure that the application and the coordinator are running on different ports, here I use 8081 for the application with the coordinator listening on port 8080 which is the default (I will show in a later blog how to change the default location of the coordinator).

Make a REST request to the hello method:

curl http://localhost:8081/hello

Just before the hello method is invoked an LRA will be started and the participant resource will be enlisted with the LRA. After the method finishes the LRA will be ended automatically (which is the default behaviour of the @LRA annotation). Ending the LRA triggers the termination phase in which the coordinator will invoke the @Complete method (called completeWork in the example) or the @Compensate method (called compensateWork in the example) of each enlisted participant depending on whether the LRA is closing or cancelling. If you want to verify that things are working as expected try updating the resource example to print the value of the HTTP header called Long-Running-Action (see the Java constant LRA_HTTP_CONTEXT_HEADER) which gets injected as a method parameter to each of the annotated methods. Alternatively run the participant in a debugger, for example if you break point inside the hello method and inspect the lraId method parameter and then compare it with what the coordinator knows (curl http://localhost:8080/lra-coordinator) then you should notice that the LRA is in the Active state. Then release the debugger and check back with the coordinator (the LRA will be gone since it should have completed). Note also that the lraId parameter should be the same as the one passed to the @Complete method so setting a break point in that method may also be illuminating.

Recap of what we’ve said before about compensations

12/2017 Narayana LRA: implementation of saga transactions

In this blog Ondra Chaloupka provided an overview of the Saga pattern and then identified those features of LRA that implement the pattern. His article also provided links to the Narayana code and quickstarts, and in particular introduced a worked example of how to run it in a cloud based environment using Minishift (OpenShift on your laptop).

12/2017 Saga implementations comparison

Another interesting article contributed by Ondra where he takes a different approach to explaining the concepts and mechanics of LRA’s. In this essay he compares and contrasts the Narayana LRA implementation with two popular Saga implementations: Axon framework and Eventuate.io. This approach is particularly useful for users already familiar with these other frameworks to get a rapid understanding of what LRA is offering.

11/2017 A comparison of Long Running Actions with a recent WSO paper

Tom Jenkinson proves "a high-level comparison of the approach taken by the LRA framework with a paper released to the 2017 IEEE 24th International Conference on Web Services - “WSO: Developer-Oriented Transactional Orchestration of Web-Services”." describing the various concepts introduced in both approaches: ordering compensations, idempotency, structure, ease of use, locking and orchestration and nesting of activities.

Tom describes LRA thus: "This specification is tailored to addressing needs of applications which are running in highly concurrent environments and have the need to ensure updates to multiple resources have atomic outcomes, but where locking of the resource manager has an unacceptable impact on the overall throughput of the system. LRA has been developed using a cloud first philosophy and achieves its goal by providing an extended transaction model based on Sagas. It provides a set of APIs and components designed to work well in typical microservice architectures."

06/2017 Sagas and how they differ from two-phase commit

Yet another article provided by Ondra, a busy year for him. Each of Ondra’s 2017 articles, though focused on communicating what LRA is and when, where and why it can be useful, do an excellent job at covering different facets of the compensation based approach to achieving distributed consistency. Ondra provides an extensive overview of the various concepts involved in these two transaction models [sagas and LRA’s].

Of particular interest is the extensive set of references provided at the end of the blog.

10/2016 Achieving Consistency in a Microservices Architecture

This article provides an overview of the problem domain that LRA addresses and draws attention to some of the difficulties that naive approaches run into when attempting their resolution. Although the article, written in 2016, predates the Narayana LRA implementation it does outline the basic LRA protocol.

07/2013 Compensating Transactions: When ACID is too much

An epic four part series contributed by Paul Robinson covering many aspects of the compensation based approach to transactions:

Part 2: Non-transactional Work. This part will cover situations where you need to coordinate multiple non-transactional resources, such as sending an email or invoking a third party service.

Part 3: Cross-domain Distributed Transactions: This part covers a scenario where the transaction is distributed, and potentially crosses multiple business domains.

Part 4: Long-lived Transactions. This part covers transactions that span long periods of time and shows how it’s possible to continue the transaction even if some work fails.

05/2015 XA and microservices, 05/2014 Transactions and Microservices and 04/2015 Microservices and transactions - an update

Three posts in which Mark allays a number of fears, concerns and fallacies that developers may have with combining transactions with microservices.

03/2011 Slightly alkaline transactions if you please …

Mark introduces his short post with: "Given that the traditional ACID transaction model is not appropriate for long running/loosely coupled interactions, let’s pose the question, “what type of model or protocol is appropriate?”", and then he goes on to answer the question he poses. Along the way we find definitions and links to papers that define various extensions to the traditional model giving us the "lay of land", so to speak, to enable us to navigate our way to an understanding of alternate models.

03/2011 When ACID is too strong

Another short post in which Mark presents the motivation for long-running activities.

03/2011 REST, Cloud and transactions

Mark motivates the case for REST based transaction protocols as a complement to WS-Transactions. LRA is such a REST based protocol and his post provides important background material on why LRA exists alongside WS-BA.

10/2011 nested transactions 101

Some useful background information on nested transactions (partially motivates nested LRA’s)