MicroProfile LRA: A Comprehensive Guide

In today’s distributed and microservices-based architectures, ensuring data consistency and coordination across multiple services can be challenging. This is where MicroProfile LRA (Long Running Actions) comes into play. In this tutorial, we will explore the fundamentals of MicroProfile LRA, how to use it effectively, and compare it with other standards such as distributed transactions.

What is MicroProfile LRA?

MicroProfile LRA is an important specification that provides a standard way to handle distributed transactions in a microservices environment. It enables you to define a coordinated workflow across multiple services, ensuring that all actions either commit or roll back together. This is crucial for maintaining data integrity and consistency.

Key Features of MicroProfile LRA

Before we delve into the details of how to use MicroProfile LRA, let’s highlight its key features:

  1. Distributed Coordination: MicroProfile LRA facilitates coordination and communication between different microservices involved in a transaction.
  2. Compensating Actions: In case of failure, MicroProfile LRA allows you to define compensating actions that can reverse the effects of previous actions, ensuring that the system reaches a consistent state.
  3. Timeouts: LRA defines timeouts for each action, ensuring that resources are not locked indefinitely in case of failure.
  4. Compensation Completion: LRA provides a mechanism to notify participants when a compensating action has completed, enabling them to clean up any temporary resources.
microprofile lra

Besides, we need to understand which are the two main components of Microprofile LRA:

  • The Coordinator is a central component that manages and coordinates the Long Running Action (LRA) transactions.It oversees the lifecycle of LRAs, including starting, ending, and monitoring transactions.
  • The Participant is a service or component that participates in the Long Running Action (LRA) transaction.It can be any microservice or resource that needs to ensure transactional consistency and coordination.

Now that we understand the basics of MicroProfile LRA, let’s explore how to use it in your microservices-based application.

Step-by-Step Guide to Using MicroProfile LRA

Follow these steps to integrate MicroProfile LRA into your application:

Step 1: Add MicroProfile LRA Dependency

Include the MicroProfile LRA dependency in your project’s build configuration. For Maven projects, add the following dependency to your pom.xml:

<dependency>
      <groupId>org.eclipse.microprofile.lra</groupId>
      <artifactId>microprofile-lra-api</artifactId>
      <version>2.0</version> 
      <scope>provided</scope>
</dependency>

Step 2: Configure MicroProfile LRA Subsystem

MicroProfile LRA is available out of the box since WildFly 28. To activate it, we need to enable the Coordinator and Participant subsystem in it.

Firstly, start WIldFly using the standalone-microprofile.xml configuration:

./standalone.sh -c standalone-microprofile.xml

Open the CLI, and install the lra-coordinator extension and subsystem in WildFly:

/extension=org.wildfly.extension.microprofile.lra-coordinator:add

/subsystem=microprofile-lra-coordinator:add

reload

Next, install also the lra-participant so that you are able to create transactions from within WildFly:

/extension=org.wildfly.extension.microprofile.lra-participant:add

/subsystem=microprofile-lra-participant:add
reload

Step 3: Annotate Your Service

Annotate the services involved in the transaction with the @LRA annotation. This informs MicroProfile LRA that the service participates in a long-running action.

@Path("/lra")
@LRA(LRA.Type.SUPPORTS)
public class TransactionParticipant {

    @Inject
    TransactionUtil txUtil;

    @LRA
    @GET
    @Path("/begin")
    public Response begin(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId,
            @QueryParam("name") @DefaultValue("guest") String name) {
        System.out.println("Start Transaction with LRA ID = " + lraId);

        try {
            Files.write(Paths.get(txUtil.getId(lraId)), name.getBytes());
        } catch (IOException e) {

            e.printStackTrace();
        }

        return txUtil.checkExpression(name) ? Response.ok().build() : Response.status(500).build();
    }

}

The LRA.Type.SUPPORTS annotation indicates that a service method supports participating in an LRA if one is already active, but it does not start a new LRA if one is not already active.

The actual transaction starts in the begin method which also contains the @LRA annotation. It signifies that this method is responsible for starting a new LRA transaction. The MicroProfile LRA implementation intercepts the incoming HTTP request and extracts the value of the specified header (LRA.LRA_HTTP_CONTEXT_HEADER). It then injects this value into the corresponding parameter (lraId) of the annotated method.

The begin method performs a simple file system transaction by writing the a file name with the lraId. Our commit rule is quite simple, if the “name” parameter matches a regular expression (at least a lowercase and uppercase and number character) then we will complete the transaction by issuing a Response.Ok . Otherwise, we will trigger a compensation by issuing a Response Error.

Step 4: Declare complete and compensation actions

Finally, we will declare the actions which will be triggered when the LRA transaction completes or rollbacks.

@Complete
@PUT
@Path("/complete")
public Response complete(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId) {
   System.out.println("Complete ID = " + lraId);
   new File(txUtil.getId(lraId)).renameTo(new File(txUtil.getId(lraId) + ".done"));

   return Response.ok().build();
}

@Compensate
@PUT
@Path("/compensate")
public Response compensate(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId) {
   System.out.println("Compensate ID = " + lraId);
   new File(txUtil.getId(lraId)).renameTo(new File(txUtil.getId(lraId) + ".failed"));
  
   return Response.ok().build();
}

Each of these actions will rename the File created for this transaction.

  • If the transaction successfully completes, we will add the suffix “.done” to the file
  • Otherwise, we will add the suffix “.failed” to the file.

Step 5: Test and Verify

Test your application by triggering the transaction and verifying the behavior. For example, let’s test the endpoint by passing as name one name which matches complies with the regular expression check:

curl localhost:8080/mp-lra/lra/begin?name=Frank123

From the Console Logs you should be able to see that the Transaction completest successfully:

16:48:44,818 INFO  [stdout] (default task-2) Start Transaction with LRA ID = http://localhost:8080/lra-coordinator/lra-coordinator/0_ffff7f000001_-642849cb_646e145c_269
16:48:44,828 INFO  [stdout] (default task-21) Complete ID = http://localhost:8080/lra-coordinator/lra-coordinator/0_ffff7f000001_-642849cb_646e145c_269

Besides, you should see that a File name is available with the LraId in the current directory of the JVM which has the suffix “.done”.

0_ffff7f000001_-642849cb_646e145c_269.done

Then, test again the Endpoint with a name that is not compliant, therefore a compensation action will follows:

curl localhost:8080/mp-lra/lra/begin?name=abc

As you can see from the Console, this time a compensation action will trigger following the transaction start:

16:48:32,805 INFO  [stdout] (default task-2) Start Transaction with LRA ID = http://localhost:8080/lra-coordinator/lra-coordinator/0_ffff7f000001_-642849cb_646e145c_264
16:48:32,814 INFO  [stdout] (default task-21) Compensate ID = http://localhost:8080/lra-coordinator/lra-coordinator/0_ffff7f000001_-642849cb_646e145c_264

Also, you should be able to see a File name with the .failed suffix in the JVM directory:

0_ffff7f000001_-642849cb_646e145c_264.failed

Congratulations! You have successfully integrated MicroProfile LRA into your microservices application, ensuring transactional consistency and coordination.

Source code for this example: https://github.com/fmarchioni/mastertheboss/tree/master/micro-services/mp-lra

A special thanks to colleagues from Red Hat Martin Stefanko and Manuel Finelli for suggestions on how to set up and code this quickstart.

MicroProfile LRA vs. Other Standards

MicroProfile LRA provides several advantages over traditional distributed transaction standards. Let’s compare it with another standard such as Two-Phase Commit (2PC):

MicroProfile LRA vs. Two-Phase Commit (2PC)

  • Flexibility: MicroProfile LRA allows for compensating actions, providing flexibility in handling failures and allowing partial rollbacks. In contrast, 2PC is an all-or-nothing approach, where a single failure can cause the entire transaction to abort.
  • Decentralization: MicroProfile LRA operates in a decentralized manner, with coordination happening at the service level. This reduces the reliance on a central coordinator and improves scalability. 2PC requires a central coordinator, which can become a bottleneck in highly distributed systems.

In summary, MicroProfile LRA offers a flexible and decentralized approach to handling distributed transactions, making it a powerful choice for microservices architectures.

Conclusion

MicroProfile LRA is a valuable specification for managing distributed transactions in microservices-based applications. By following the steps outlined in this tutorial, you can seamlessly integrate MicroProfile LRA into your project, ensuring data consistency and coordination across services. Compared to other standards, MicroProfile LRA provides flexibility, decentralized coordination, and built-in compensation mechanisms.

References: https://github.com/wildfly/wildfly.org/blob/78687bc92a021b54ba0cfb4cc808d6feae40fab3/_posts/2023-05-25-MicroProfile-LRA.adoc

Found the article helpful? if so please follow us on Socials