Long Running Actions for MicroProfile on Helidon… Data Integrity for Microservices.
Microservices create challenges for data consistency and integrity that necessitate changes in the transaction processing and data patterns used by them.
Traditional systems rely on two-phase commit and XA protocols that use synchronous communication, locking of resources, and recovery via rollback (or commit as the case may be). While this provides strong consistency and isolation it does not scale well in a microservices environment due to the latency of held locks and, therefore, is suitable for only a small subset of such use cases (generally those with lower throughput requirements)
The saga pattern uses asynchronous communication and local resources only (no distributed locks) and recovery via compensating actions. This scales well and so is well suited for long running transactions in a microservices environment. Additional application design considerations are necessary, however, for read isolation and compensation logic and debugging can be difficult.
The MicroProfile LRA, as stated at https://github.com/eclipse/microprofile-lra , “[…]introduces APIs for services to coordinate activities.
The main thrust of the proposal introduces an API for loosely coupled services to coordinate long running activities in such a way as to guarantee a globally consistent outcome without the need to take locks on data.”
In many ways, the Long Running Activities (LRA) APIs are to Sagas between microservices what the Java Transaction API (JTA) is to two-phase commit and XA for traditional applications (or for contained use within a single microservice).
Much is written on this topic already and so the focus here is on running LRA in Helidon. Helidon uses the Narayana implementation of the LRA specification.
Using LRA in Helidon MP
There are two main components involved:
- Participants: these are the JAX-RS microservices that will be involved in the LRA and are annotated with the standard
org.eclipse.microprofile.lra.annotation.ws.rs.LRA
values listed in the following table from the specification…
- The LRA coordinator: this is a devoted service that the participants implicitly register with (as a result of their LRA annotations) and is responsible for driving a LRA to complete, compensate, etc..
Update your LRA participant pom.xml (s)
Add dependencies for LRA annotations and runtime.
While not necessary for runtime, a build-time LRA annotation checker can be used.
Add LRA feature in your application
Your MP javax.ws.rs.core.Application will likely already have code similar to this. Just add the line that includes the DynamicFeatureio.narayana.lra.filter.FilterRegistration
:
Annotate the endpoints
Add LRA annotations to your microservice endpoints.
In this example we have two services involved in a LRA which are an order service and and inventory service.
Order Service:
- call to
placeOrder
by client begins an LRA due to@LRA(value = LRA.Type.RequiresNew)
annotation and registers service with the LRA coordinator - calls inventory service to check inventory with/propagating
LRA_HTTP_CONTEXT_HEADER
with the LRA id (achieved by registering client LRA filters) . - if inventory exists (the default) success is returned to order service which implicitly completes the LRA and the coordinator calls the
@Complete
annotated methodcompleteOrder
- if inventory does not exist (achieved by calling the removeInventory endpoint on inventory service) failure is returned to order service which cancels the LRA and the coordinator calls the
@Compensate
annotated methodcancelOrder
Inventory Service:
- call to
reserveInventoryForOrder
by order service is executed within LRA started and propagated by order service due to@LRA(value = LRA.Type.Mandatary)
annotation and registers service with the LRA coordinator - checks inventory and returns the result (success or fail) to the order service
Build and deploy the LRA coordinator
Add dependencies to build and run the LRA coordinator service jar.
Build and start the LRA app
Run the example ./build.sh
script to build and package the order and inventory participant services and the coordinator.
Start the services.
Run the LRA app for the success case
Notice the output from the order service:
OrderResource.placeOrder in LRA due to LRA.Type.REQUIRES_NEW lraId:[...]
OrderResource.placeOrder response from inventory:inventorysuccess
OrderResource.completeOrder
Notice the output from the inventory service:
InventoryResource.addInventory
InventoryResource.placeOrder in LRA due to LRA.Type.MANDATORY lraID:[...]
InventoryResource.completeOrder prepare item for shipping lraId:[...]
Run the LRA app for the compensating case
Notice the output from the order service:
OrderResource.placeOrder in LRA due to LRA.Type.REQUIRES_NEW lraId:[...]
OrderResource.placeOrder response from inventory:inventoryfailure
OrderResource.cancelOrder
Notice the output from the inventory service:
InventoryResource.removeInventory
InventoryResource.placeOrder in LRA due to LRA.Type.MANDATORY lraID:[...]
InventoryResource.cancelOrder put inventory back lraId:[...]
What next?
The LRA specification is currently at 88% completion and expected to be included in the next MicroProfile release. Progress can be followed here: https://github.com/eclipse/microprofile-lra/milestone/1
Helidon will continue to facilitate this and other areas of microservice data patterns upon this by providing.
- Equivalent Helidon SE support
- Messaging (and event sourcing) features
- HA configurations
- Database integration features
The example can be found here: https://github.com/paulparkinson/helidon-examples-lra