Apoio ao Participante do Narayana LRA
Introdução
A extensão do participante LRA (abreviação de Long Running Action) é útil em projetos baseados em microsserviços, nos quais diferentes serviços podem se beneficiar de uma noção relaxada de consistência distribuída.
A ideia é que vários serviços executem diferentes cálculos/ações em conjunto, mantendo a opção de compensar quaisquer ações executadas durante o cálculo. Esse tipo de acoplamento frouxo de serviços preenche a lacuna entre modelos de consistência fortes, como JTA/XA, e soluções de consistência ad hoc "caseiras".
O modelo baseia-se no especificação do Eclipse MicroProfile LRA . A abordagem é que o desenvolvedor anote um método de negócios com uma anotação Java ( @LRA ). Quando esse método é chamado, é criado um contexto de LRA (se ainda não houver um), que é transmitido junto com as invocações subsequentes do Jakarta REST até que seja alcançado um método que também contenha uma anotação @LRA
com um atributo que indique que o LRA deve ser fechado ou cancelado. O padrão é que o LRA seja fechado no mesmo método que iniciou o LRA (que pode ter propagado o contexto durante a execução do método). O recurso Jakarta REST indica que deseja participar da interação, no mínimo, marcando um dos métodos com uma anotação @Compensate . Se o contexto for cancelado posteriormente, essa ação @Compensate
tem a garantia de ser chamada mesmo na presença de falhas e é o gatilho para o recurso compensar todas as atividades realizadas no contexto do LRA. Essa garantia permite que os serviços operem de forma confiável com a garantia de consistência eventual (quando todas as atividades de compensação tiverem sido concluídas). O participante pode pedir para ser notificado de forma confiável quando o LRA do qual está participando for encerrado, marcando um dos métodos com uma anotação @Complete . Dessa forma, o cancelamento de um LRA faz com que todos os participantes sejam notificados por meio de sua chamada de retorno Compensar e o fechamento de um LRA faz com que todos os participantes sejam notificados por meio de sua chamada de retorno Concluir (se houver). Outras anotações para controle de participantes estão documentadas no javadoc da API MicroProfile LRA v1.0 .
Configuração
Depois de configurar o seu projeto Quarkus Maven, é possível adicionar a extensão narayana-lra
executando o seguinte comando no diretório base do seu projeto:
quarkus extension add narayana-lra,resteasy-jackson,resteasy-client-jackson
./mvnw quarkus:add-extension -Dextensions='narayana-lra,resteasy-jackson,resteasy-client-jackson'
./gradlew addExtension --extensions='narayana-lra,resteasy-jackson,resteasy-client-jackson'
Isto irá adicionar o seguinte trecho no seu arquivo de build:
<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-resteasy-client-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-narayana-lra")
implementation("io.quarkus:quarkus-resteasy-jackson")
implementation("io.quarkus:quarkus-resteasy-client-jackson")
|
Se exitir um coordenador em execução, então isto é tudo o que você precisa para criar novas LRAs e alistar participantes nelas.
A extensão LRA pode ser configurada atualizando um arquivo application.properties
no diretório src/main/resources
. A única propriedade específica do LRA é quarkus.lra.coordinator-url=<url>
, que especifica o ponto de extremidade HTTP de um coordenador externo, por exemplo:
quarkus.lra.coordinator-url=http://localhost:8080/lra-coordinator
Para um coordenador Narayana, o componente de caminho da url é normalmente lra-coordinator
. Os coordenadores podem ser obtidos em https://quay.io/repository/jbosstm/lra-coordinator ou você pode criar seu próprio coordenador usando um maven pom que inclua as dependências apropriadas. Um quickstart do Quarkus será fornecido para mostrar como fazer isso, ou você pode dar uma olhada em um dos quickstarts do Narayana . Outra opção seria executá-lo gerenciado dentro de um servidor de aplicativos WildFly.
Manuseio de falhas
Quando uma LRA é instruída a finalizar, ou seja, quando um método anotado com @LRA(end = true, …) é invocado, o coordenador instruirá todos os serviços envolvidos na interação a finalizar. Se um serviço estiver indisponível (ou ainda finalizando), o coordenador tentará novamente periodicamente. É responsabilidade do usuário reiniciar os serviços com falha no mesmo ponto de extremidade que usaram quando se juntaram à LRA pela primeira vez, ou informar ao coordenador que desejam ser notificados em novos pontos de extremidade. Uma LRA não é considerada finalizada até que todos os participantes tenham confirmado que finalizaram.
O coordenador é responsável por criar e finalizar LRAs de forma confiável e por gerenciar o de participantes e, portanto, deve estar disponível (por exemplo, se ele ou a rede falharem, algo no ambiente será responsável por reiniciar o coordenador ou por reparar a rede, respectivamente). Para cumprir essa tarefa, o coordenador deve ter acesso a um armazenamento durável para seus registros (por meio de um sistema de arquivos ou em um banco de dados). No momento em que este artigo foi escrito, o gerenciamento de coordenadores é responsabilidade do usuário. Uma solução "pronta para uso" será apresentada em breve.
Exemplos
A seguir, um exemplo simples de como iniciar um LRA e como receber uma notificação quando o LRA for cancelado posteriormente (o método anotado @Compensate
é chamado) ou fechado ( @Complete
é chamado):
@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 Jakarta REST 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 Jakarta REST 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 Jakarta REST 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();
}
}
O exemplo também mostra que, quando um LRA está presente, seu identificador pode ser obtido pela leitura dos cabeçalhos da solicitação por meio do tipo de anotação @HeaderParam
Jakarta REST.
E aqui está um exemplo de como iniciar um LRA em um método de recurso e fechá-lo em um método de recurso diferente usando o elemento end
da anotação LRA
. Ele também mostra como configurar o LRA para ser cancelado automaticamente se o método de negócios retornar os códigos de status HTTP específicos identificados nos elementos cancelOn
e cancelOnFamily
:
@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 { ... }
O elemento end = false
no método bookTrip força o LRA a continuar em execução quando o método termina e o elemento end = true
no método confirmTrip força o LRA (iniciado pelo método bookTrip) a ser fechado quando o método termina. Observe que esse elemento final pode ser colocado em qualquer recurso Jakarta REST (ou seja, um serviço pode iniciar o LRA enquanto outro serviço diferente o encerra). Há muitos outros exemplos no documento de especificação do Microprofile LRA e no Microprofile LRA TCK .