mp.lra.adoc Maven / Gradle / Ivy
The newest version!
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2021, 2024 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
///////////////////////////////////////////////////////////////////////////////
= Long Running Actions (LRA)
:description: Long Running Actions
:keywords: helidon, mp, lra
:h1Prefix: MP
:feature-name: Long Running Actions
:spec-version: 1.0-RC3
:spec-name: MicroProfile {feature-name} specification
:javadoc-link: https://download.eclipse.org/microprofile/microprofile-lra-{spec-version}/apidocs/org/eclipse/microprofile/lra/annotation/
:rootdir: {docdir}/..
include::{rootdir}/includes/mp.adoc[]
== Contents
* <>
* <>
* <>
* <>
* <>
* <>
* <>
** <>
** <>
** <>
* <>
== Overview
Distributed transactions for microservices are known as SAGA design patterns and are defined by the {microprofile-lra-spec-url}[{spec-name}].
Unlike well known XA protocol, LRA is asynchronous and therefore much more scalable. Every LRA JAX-RS resource (<>) defines endpoints to be invoked when transaction needs to be _completed_ or _compensated_.
include::{rootdir}/includes/dependencies.adoc[]
[source,xml]
----
io.helidon.microprofile.lra
helidon-microprofile-lra
io.helidon.lra
helidon-lra-coordinator-narayana-client
----
== Usage
The LRA transactions need to be coordinated over REST API by the LRA coordinator. <>
keeps track of all transactions and calls the `@Compensate` or `@Complete` endpoints for all participants involved in the particular
transaction. LRA transaction is first started, then joined by <>.
The participant reports the successful finish of the transaction by calling it complete. The coordinator then calls the JAX-RS
_complete_ endpoint that was registered during the join of each
<>. As the completed or compensated participants don't have to be on same instance,
the whole architecture is highly scalable.
image::lra/lra-complete-lb.svg[Complete]
If an error occurs during the LRA transaction, the participant reports a cancellation of LRA to the coordinator. <> calls compensate on all the joined participants.
image::lra/lra-compensate-lb-error.svg[Cancel]
When a participant joins the LRA with timeout defined `@LRA(value = LRA.Type.REQUIRES_NEW, timeLimit = 5, timeUnit = ChronoUnit.MINUTES)`, the coordinator compensates if the timeout occurred before the close is reported by the participants.
image::lra/lra-compensate-lb-timeout.svg[Timeout]
== API
=== Participant
The Participant, or Compensator, is an LRA resource with at least one of the JAX-RS(or non-JAX-RS) methods annotated with
{javadoc-link}Compensate.html[@Compensate] or {javadoc-link}AfterLRA.html[@AfterLRA].
=== @LRA [[lra-method]]
{javadoc-link}ws/rs/LRA.html[~javadoc~]
Marks JAX-RS method which should run in LRA context and needs to be accompanied by at least minimal set of mandatory
participant methods(<> or <>).
LRA options:
* {javadoc-link}ws/rs/LRA.html#value--[value]
** {javadoc-link}ws/rs/LRA.Type.html#REQUIRED[REQUIRED] join incoming LRA or create and join new
** {javadoc-link}ws/rs/LRA.Type.html#REQUIRES_NEW[REQUIRES_NEW] create and join new LRA
** {javadoc-link}ws/rs/LRA.Type.html#MANDATORY[MANDATORY] join incoming LRA or fail
** {javadoc-link}ws/rs/LRA.Type.html#SUPPORTS[SUPPORTS] join incoming LRA or continue outside LRA context
** {javadoc-link}ws/rs/LRA.Type.html#NOT_SUPPORTED[NOT_SUPPORTED] always continue outside LRA context
** {javadoc-link}ws/rs/LRA.Type.html#NEVER[NEVER] Fail with 412 if executed in LRA context
** {javadoc-link}ws/rs/LRA.Type.html#NESTED[NESTED] create and join new LRA nested in the incoming LRA context
* {javadoc-link}ws/rs/LRA.html#timeLimit--[timeLimit] max time limit before LRA gets cancelled automatically by <>
* {javadoc-link}ws/rs/LRA.html#timeUnit--[timeUnit] time unit if the timeLimit value
* {javadoc-link}ws/rs/LRA.html#end--[end] when false LRA is not closed after successful method execution
* {javadoc-link}ws/rs/LRA.html#cancelOn--[cancelOn] which HTTP response codes of the method causes LRA to cancel
* {javadoc-link}ws/rs/LRA.html#cancelOnFamily--[cancelOnFamily] which family of HTTP response codes causes LRA to cancel
Method parameters:
* Header {javadoc-link}ws/rs/LRA.html#LRA_HTTP_CONTEXT_HEADER[LRA_HTTP_CONTEXT_HEADER] - ID of the LRA transaction
[source,java]
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_1, indent=0]
----
=== @Compensate [[compensate-participant-method]]
{javadoc-link}Compensate.html[~javadoc~]
CAUTION: Expected to be called by LRA <> only!
Compensate method is called by a <> when LRA is cancelled,
usually by error during execution of method body of <>.
If the method responds with 500 or 202, coordinator will eventually try the call again.
If participant has <>, <>
retrieves the status to find out if retry should be done.
==== JAX-RS variant with supported LRA context values:
* Header {javadoc-link}ws/rs/LRA.html#LRA_HTTP_CONTEXT_HEADER[LRA_HTTP_CONTEXT_HEADER] - ID of the LRA transaction
* Header {javadoc-link}ws/rs/LRA.html#LRA_HTTP_PARENT_CONTEXT_HEADER[LRA_HTTP_PARENT_CONTEXT_HEADER] - parent LRA ID in case of nested LRA
[source,java]
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_2, indent=0]
----
==== Non JAX-RS variant with supported LRA context values:
* URI with LRA ID
[source,java]
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_3, indent=0]
----
=== @Complete [[complete-participant-method]]
{javadoc-link}Complete.html[~javadoc~]
CAUTION: Expected to be called by LRA <> only!
Complete method is called by <> when LRA is successfully closed.
If the method responds with 500 or 202, coordinator will eventually try the call again.
If participant has <>, <> retrieves the status to find out if retry should be done.
==== JAX-RS variant with supported LRA context values:
* Header {javadoc-link}ws/rs/LRA.html#LRA_HTTP_CONTEXT_HEADER[LRA_HTTP_CONTEXT_HEADER] - ID of the LRA transaction
* Header {javadoc-link}ws/rs/LRA.html#LRA_HTTP_PARENT_CONTEXT_HEADER[LRA_HTTP_PARENT_CONTEXT_HEADER] - parent LRA ID in case of nested LRA
[source,java]
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_4, indent=0]
----
==== Non JAX-RS variant with supported LRA context values:
* URI with LRA ID
[source,java]
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_5, indent=0]
----
=== @Forget
{javadoc-link}Forget.html[~javadoc~]
CAUTION: Expected to be called by LRA <> only!
<> and <>
methods can fail(500) or report that compensation/completion is in progress(202).
In such case participant needs to be prepared to report its status over <>
to <>.
When <> decides all the participants have finished, method annotated with @Forget is called.
==== JAX-RS variant with supported LRA context values:
* Header {javadoc-link}ws/rs/LRA.html#LRA_HTTP_CONTEXT_HEADER[LRA_HTTP_CONTEXT_HEADER] - ID of the LRA transaction
* Header {javadoc-link}ws/rs/LRA.html#LRA_HTTP_PARENT_CONTEXT_HEADER[LRA_HTTP_PARENT_CONTEXT_HEADER] - parent LRA ID in case of nested LRA
[source,java]
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_6, indent=0]
----
==== Non JAX-RS variant with supported LRA context values:
* URI with LRA ID
[source,java]
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_7, indent=0]
----
=== @Leave
{javadoc-link}ws/rs/Leave.html[~javadoc~]
Method annotated with @Leave called with LRA context(with header {javadoc-link}ws/rs/LRA.html#LRA_HTTP_CONTEXT_HEADER[LRA_HTTP_CONTEXT_HEADER]) informs <> that current participant is leaving the LRA.
Method body is executed after leave signal is sent.
As a result, participant methods complete and compensate won't be called when the particular LRA ends.
* Header {javadoc-link}ws/rs/LRA.html#LRA_HTTP_CONTEXT_HEADER[LRA_HTTP_CONTEXT_HEADER] - ID of the LRA transaction
[source,java]
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_8, indent=0]
----
=== @Status [[status-participant-method]]
{javadoc-link}Status.html[~javadoc~]
CAUTION: Expected to be called by LRA <> only!
If the coordinator's call to the participant's method fails, then it will retry the call.
If the participant is not idempotent, then it may need to report its state to coordinator by declaring method
annotated with @Status for reporting if previous call did change participant status.
<> can call it and decide if compensate or complete retry is needed.
==== JAX-RS variant with supported LRA context values:
* Header {javadoc-link}ws/rs/LRA.html#LRA_HTTP_CONTEXT_HEADER[LRA_HTTP_CONTEXT_HEADER] - ID of the LRA transaction
* {javadoc-link}ParticipantStatus.html[ParticipantStatus] - Status of the participant reported to <>
[source,java]
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_9, indent=0]
----
==== Non JAX-RS variant with supported LRA context values:
* URI with LRA ID
* {javadoc-link}ParticipantStatus.html[ParticipantStatus] - Status of the participant reported to <>
[source,java]
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_10, indent=0]
----
=== @AfterLRA [[after-participant-method]]
{javadoc-link}AfterLRA.html[~javadoc~]
CAUTION: Expected to be called by LRA <> only!
Method annotated with {javadoc-link}AfterLRA.html[@AfterLRA] in the same class as the one with @LRA annotation gets invoked after particular LRA finishes.
==== JAX-RS variant with supported LRA context values:
* Header {javadoc-link}ws/rs/LRA.html#LRA_HTTP_ENDED_CONTEXT_HEADER[LRA_HTTP_ENDED_CONTEXT_HEADER] - ID of the finished LRA transaction
* Header {javadoc-link}ws/rs/LRA.html#LRA_HTTP_PARENT_CONTEXT_HEADER[LRA_HTTP_PARENT_CONTEXT_HEADER] - parent LRA ID in case of nested LRA
* {javadoc-link}LRAStatus.html[LRAStatus] - Final status of the LRA ({javadoc-link}LRAStatus.html#Cancelled[Cancelled], {javadoc-link}LRAStatus.html#Closed[Closed], {javadoc-link}LRAStatus.html#FailedToCancel[FailedToCancel], {javadoc-link}LRAStatus.html#FailedToClose[FailedToClose])
[source,java]
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_11, indent=0]
----
==== Non JAX-RS variant with supported LRA context values:
* URI with finished LRA ID
* {javadoc-link}LRAStatus.html[LRAStatus] - Final status of the LRA ({javadoc-link}LRAStatus.html#Cancelled[Cancelled], {javadoc-link}LRAStatus.html#Closed[Closed], {javadoc-link}LRAStatus.html#FailedToCancel[FailedToCancel], {javadoc-link}LRAStatus.html#FailedToClose[FailedToClose])
[source,java]
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_12, indent=0]
----
== Configuration
[source,text]
.Type
----
io.helidon.microprofile.lra
----
.Optional configuration options
[cols="3,3,2,5a"]
|===
|Key |Type |Default value |Description
|`mp.lra.coordinator.url` |string |`\http://localhost:8070/lra-coordinator` |Url of coordinator.
|`mp.lra.coordinator.propagation.active` |boolean |{nbsp} |Propagate LRA headers `LRA_HTTP_CONTEXT_HEADER` and `LRA_HTTP_PARENT_CONTEXT_HEADER` through non-LRA endpoints.
|`mp.lara.participant.url` |string |{nbsp} |Url of the LRA enabled service overrides standard base uri, so coordinator can call load-balancer instead of the service.
|`mp.lra.coordinator.timeout` |string |{nbsp} |Timeout for synchronous communication with coordinator.
|`mp.lra.coordinator.timeout-unit` |string |{nbsp} |Timeout unit for synchronous communication with coordinator.
|===
[source,yaml]
.Example of LRA configuration
----
mp.lra:
coordinator.url: http://localhost:8070/lra-coordinator <1>
propagation.active: true <2>
participant.url: https://coordinator.visible.host:443/awesomeapp <3>
----
<1> Url of coordinator
<2> Propagate LRA headers LRA_HTTP_CONTEXT_HEADER and LRA_HTTP_PARENT_CONTEXT_HEADER through non-LRA endpoints
<3> Url of the LRA enabled service overrides standard base uri,
so coordinator can call load-balancer instead of the service
For more information continue to {microprofile-lra-spec-url}[{spec-name}].
== Examples
The following example shows how a simple LRA participant starts and joins a transaction after calling the '/start-example' resource.
When startExample method finishes successfully, close is reported to <>
and `/complete-example` endpoint is called by coordinator to confirm successful closure of the LRA.
If an exception occurs during startExample method execution, coordinator receives cancel call and `/compensate-example`
is called by coordinator to compensate for cancelled LRA transaction.
[source,java]
.Example of simple LRA participant
----
include::{sourcedir}/mp/LraSnippets.java[tag=snippet_13, indent=0]
----
<1> This JAX-RS PUT method will start new LRA transactions and join it before method body gets executed
<2> LRA ID assigned by coordinator to this LRA transaction
<3> When method execution finishes exceptionally, cancel signal for this particular LRA is sent to coordinator
<4> When method execution finishes successfully, complete signal for this particular LRA is sent to coordinator
<5> Method which will be called by coordinator when LRA is completed
<6> Method which will be called by coordinator when LRA is canceled
== Additional Information
=== Coordinator
Coordinator is a service that tracks all LRA transactions and calls the compensate REST endpoints of
the participants when the LRA transaction gets cancelled or completes (in case it gets closed).
In addition, participant also keeps track of timeouts, retries participant calls, and assigns LRA ids.
.Helidon LRA supports following coordinators
* Helidon LRA coordinator
* https://narayana.io/lra[Narayana coordinator].
=== Helidon LRA Coordinator
CAUTION: Experimental tool, usage in production is not advised.
[source,bash]
.Build and run Helidon LRA coordinator
----
docker build -t helidon/lra-coordinator https://github.com/oracle/helidon.git#:lra/coordinator/server
docker run --name lra-coordinator --network="host" helidon/lra-coordinator
----
Helidon LRA coordinator is compatible with Narayana clients, you need to add a dependency for Narayana client:
[source,xml]
.Dependency needed for using Helidon LRA with Narayana compatible coordinator
----
io.helidon.lra
helidon-lra-coordinator-narayana-client
----
=== Narayana
https://narayana.io[Narayana] is a transaction manager supporting LRA.
To use Narayana LRA coordinator with Helidon LRA client you need to add a dependency for Narayana client:
[source,xml]
.Dependency needed for using Helidon LRA with Narayana coordinator
----
io.helidon.lra
helidon-lra-coordinator-narayana-client
----
The simplest way to run Narayana LRA coordinator locally:
[source,bash]
.Downloading and running Narayana LRA coordinator
----
curl https://repo1.maven.org/maven2/org/jboss/narayana/rts/lra-coordinator-quarkus/5.11.1.Final/lra-coordinator-quarkus-5.11.1.Final-runner.jar \
-o narayana-coordinator.jar
java -Dquarkus.http.port=8070 -jar narayana-coordinator.jar
----
Narayana LRA coordinator is running by default under `lra-coordinator` context,
with port `8070` defined in the snippet above you need to configure your Helidon LRA app as follows:
`mp.lra.coordinator.url=http://localhost:8070/lra-coordinator`
== Reference
* https://github.com/eclipse/microprofile-lra[MicroProfile LRA GitHub Repository]
* {microprofile-lra-spec-url}[{spec-name}]
* https://download.eclipse.org/microprofile/microprofile-lra-{spec-version}/apidocs/org/eclipse/microprofile/lra/[Microprofile LRA JavaDoc]
* https://helidon.io/docs/v4/apidocs/io.helidon.lra.coordinator.client/module-summary.html[Helidon LRA Client JavaDoc]