Ad esempio, stai costruendo una piattaforma di e-commerce con microservizi per l'inventario, il pagamento e la spedizione. Un cliente effettua un ordine e improvvisamente ti trovi di fronte alla vecchia domanda: come garantire che tutti questi servizi funzionino bene insieme senza farti impazzire?

Le transazioni ACID tradizionali sono ottime per i monoliti, ma non funzionano nel mondo dei microservizi. È come cercare di usare un martello per rompere una noce: eccessivo e probabilmente causa più problemi di quanti ne risolva. È qui che entra in gioco LRA per salvare la situazione.

Perché ACID non è sufficiente:

  • Accoppiamento stretto tra i servizi (un grande no-no nei microservizi)
  • Collo di bottiglia delle prestazioni a causa del blocco
  • Problemi di scalabilità man mano che il sistema cresce

LRA adotta un approccio diverso. Invece di forzare transazioni atomiche tra i servizi, abbraccia il modello di consistenza eventuale. È come coordinare una coreografia in cui ogni ballerino (servizio) conosce la sua parte e sa come recuperare se qualcuno pesta i piedi.

MicroProfile LRA

Quindi, cos'è esattamente MicroProfile LRA? Pensalo come un coreografo per il tuo balletto di microservizi. Fornisce un modo standardizzato per gestire operazioni distribuite a lungo termine che coinvolgono più servizi.

Concetti chiave:

  • Azioni a lungo termine: Operazioni che possono richiedere secondi, minuti o addirittura ore per essere completate
  • Compensazione: La capacità di annullare azioni se qualcosa va storto
  • Consistenza eventuale: Accettare il fatto che la consistenza tra i servizi richiede tempo

LRA non cerca di forzare la consistenza immediata. Invece, ti offre strumenti per gestire il ciclo di vita di queste operazioni a lungo termine e garantire che, alla fine, il tuo sistema raggiunga uno stato consistente.

Configurazione di LRA: Guida passo passo

Pronto ad aggiungere un po' di magia LRA al tuo progetto? Vediamo come configurarlo:

1. Aggiungi la dipendenza MicroProfile LRA

Per prima cosa, dovrai aggiungere la dipendenza MicroProfile LRA al tuo progetto. Se stai usando Maven, aggiungi questo al tuo pom.xml:


<dependency>
    <groupId>org.eclipse.microprofile.lra</groupId>
    <artifactId>microprofile-lra-api</artifactId>
    <version>1.0</version>
</dependency>

2. Configura il tuo coordinatore LRA

Avrai bisogno di un coordinatore LRA per gestire le tue LRA. Questo potrebbe essere un servizio separato o parte della tua infrastruttura esistente. Ecco una semplice configurazione usando Quarkus:


quarkus.lra.coordinator.url=http://localhost:8080/lra-coordinator

3. Annota i tuoi metodi

Ora arriva la parte divertente: annotare i tuoi metodi per partecipare alle LRA. Ecco un semplice esempio:


@Path("/order")
public class OrderService {

    @POST
    @Path("/create")
    @LRA(LRA.Type.REQUIRED)
    public Response createOrder(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId) {
        // La tua logica di creazione dell'ordine qui
        return Response.ok().build();
    }

    @PUT
    @Path("/complete")
    @Complete
    public Response completeOrder(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId) {
        // Logica per completare l'ordine
        return Response.ok().build();
    }

    @PUT
    @Path("/compensate")
    @Compensate
    public Response compensateOrder(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId) {
        // Logica per compensare (annullare) l'ordine
        return Response.ok().build();
    }
}

In questo esempio, createOrder avvia o si unisce a un LRA, completeOrder viene chiamato quando l'LRA si completa con successo e compensateOrder viene chiamato se l'LRA deve essere annullato.

Annotazioni LRA: i tuoi nuovi migliori amici

LRA viene fornito con un set di annotazioni che rendono la gestione delle azioni a lungo termine un gioco da ragazzi. Vediamo le più importanti:

@LRA

Questa è la star dello spettacolo. Usala per definire l'ambito del tuo LRA:


@LRA(LRA.Type.REQUIRED)
public Response doSomething() {
    // Questo metodo verrà sempre eseguito all'interno di un LRA
}

Il parametro Type può essere:

  • REQUIRED: Unisciti a un LRA esistente o creane uno nuovo
  • REQUIRES_NEW: Crea sempre un nuovo LRA
  • MANDATORY: Deve essere chiamato all'interno di un LRA esistente
  • SUPPORTS: Usa un LRA se presente, altrimenti esegui senza
  • NOT_SUPPORTED: Esegui senza un LRA, anche se ne esiste uno

@Compensate

Questa è la tua rete di sicurezza. Usala per definire cosa dovrebbe accadere se qualcosa va storto:


@Compensate
public Response undoStuff(URI lraId) {
    // Logica di pulizia qui
    return Response.ok().build();
}

@Complete

Il percorso felice. Questo metodo viene chiamato quando il tuo LRA si completa con successo:


@Complete
public Response finalizeStuff(URI lraId) {
    // Logica di finalizzazione qui
    return Response.ok().build();
}

Coordinare le azioni: la danza LRA

Ora che abbiamo le nostre annotazioni in atto, vediamo come LRA coordina le azioni tra i servizi. Immagina di costruire una libreria online con servizi separati per l'inventario, il pagamento e la spedizione.

Il flusso dell'ordine:


@Path("/order")
public class OrderService {

    @Inject
    InventoryService inventoryService;
    
    @Inject
    PaymentService paymentService;
    
    @Inject
    ShippingService shippingService;

    @POST
    @Path("/place")
    @LRA(LRA.Type.REQUIRED)
    public Response placeOrder(Order order) {
        // Passo 1: Riserva l'inventario
        inventoryService.reserveBooks(order.getBooks());
        
        // Passo 2: Elabora il pagamento
        paymentService.processPayment(order.getTotal());
        
        // Passo 3: Organizza la spedizione
        shippingService.arrangeShipment(order.getShippingDetails());
        
        return Response.ok().build();
    }

    @Compensate
    public Response compensateOrder(URI lraId) {
        // Se qualcosa va storto, questo verrà chiamato per annullare tutto
        inventoryService.releaseReservation(lraId);
        paymentService.refundPayment(lraId);
        shippingService.cancelShipment(lraId);
        return Response.ok().build();
    }

    @Complete
    public Response completeOrder(URI lraId) {
        // Questo viene chiamato quando tutto ha successo
        inventoryService.confirmReservation(lraId);
        paymentService.confirmPayment(lraId);
        shippingService.confirmShipment(lraId);
        return Response.ok().build();
    }
}

In questo esempio, se un passaggio fallisce (ad esempio, il pagamento non va a buon fine), verrà chiamato il metodo @Compensate, annullando tutti i passaggi precedenti. Se tutto ha successo, il metodo @Complete finalizza l'ordine.

Timeout e politiche di cancellazione: tenere sotto controllo le tue LRA

Le LRA sono per natura a lungo termine, ma a volte "a lungo termine" si trasforma in "senza fine". Per evitare che le tue LRA vadano fuori controllo, MicroProfile LRA fornisce meccanismi per timeout e cancellazione.

Impostazione dei timeout

Puoi impostare un timeout per la tua LRA utilizzando il parametro timeLimit dell'annotazione @LRA:


@LRA(value = LRA.Type.REQUIRED, timeLimit = 10, timeUnit = ChronoUnit.MINUTES)
public Response longRunningOperation() {
    // Questa LRA scadrà automaticamente dopo 10 minuti
    // ...
}

Se l'LRA non si completa entro il tempo specificato, verrà automaticamente annullata e verrà chiamato il metodo @Compensate.

Cancellazione manuale

A volte, potresti dover annullare un LRA manualmente. Puoi farlo iniettando il LRAClient e chiamando il suo metodo cancel:


@Inject
LRAClient lraClient;

public void cancelOperation(URI lraId) {
    lraClient.cancel(lraId);
}

LRA vs. Sagas: scegliere la tua arma

A questo punto, potresti pensare: "Aspetta un attimo, questo suona molto come il pattern Saga!" Non hai torto. Le LRA e le Sagas sono come cugini nella famiglia delle transazioni distribuite. Vediamo le somiglianze e le differenze:

Somiglianze:

  • Entrambi gestiscono transazioni distribuite a lungo termine
  • Entrambi usano la compensazione per annullare il lavoro parziale
  • Entrambi mirano alla consistenza eventuale

Differenze:

  • LRA è una specifica standardizzata, mentre Saga è un pattern
  • LRA fornisce supporto integrato tramite annotazioni, rendendolo più facile da implementare
  • Le Sagas tipicamente usano eventi per il coordinamento, mentre LRA usa intestazioni HTTP

Quindi, quando dovresti usare LRA rispetto alle Sagas?

  • Se stai usando un framework compatibile con MicroProfile (come Quarkus o Open Liberty)
  • Quando vuoi un approccio standardizzato con meno codice boilerplate
  • Se preferisci uno stile più dichiarativo usando annotazioni

D'altra parte, le Sagas potrebbero essere una scelta migliore se:

  • Hai bisogno di un controllo più dettagliato sul processo di compensazione
  • Il tuo sistema è fortemente basato su eventi
  • Non stai usando un framework compatibile con MicroProfile

Monitoraggio e gestione dei cicli di vita delle LRA

Ora che abbiamo le nostre LRA in funzione, come possiamo tenerle d'occhio? Monitorare le LRA è cruciale per comprendere la salute e le prestazioni delle tue transazioni distribuite.

Metriche da monitorare

MicroProfile LRA fornisce diverse metriche pronte all'uso:

  • lra_started: Numero di LRA avviate
  • lra_completed: Numero di LRA completate con successo
  • lra_cancelled: Numero di LRA annullate
  • lra_duration: Durata delle LRA

Puoi esporre queste metriche usando MicroProfile Metrics. Ecco come potresti configurarlo in Quarkus:


quarkus.smallrye-metrics.extensions.lra.enabled=true

Integrazione con strumenti di monitoraggio

Una volta esposte le tue metriche, puoi integrarle con strumenti di monitoraggio popolari. Ecco un esempio rapido di come potresti configurare una configurazione di scraping di Prometheus:


scrape_configs:
  - job_name: 'lra-metrics'
    metrics_path: '/metrics'
    static_configs:
      - targets: ['localhost:8080']

E una semplice query per dashboard Grafana per visualizzare le durate delle LRA:


rate(lra_duration_seconds_sum[5m]) / rate(lra_duration_seconds_count[5m])

Registrazione e avvisi

Non dimenticare di impostare una corretta registrazione per le tue LRA. Ecco un semplice esempio usando SLF4J:


@Inject
Logger logger;

@LRA
public void someOperation() {
    logger.info("Avvio LRA: {}", Context.getObjectId());
    // ...
}

@Complete
public void completeOperation() {
    logger.info("Completamento LRA: {}", Context.getObjectId());
    // ...
}

@Compensate
public void compensateOperation() {
    logger.warn("Compensazione LRA: {}", Context.getObjectId());
    // ...
}

Imposta avvisi per le LRA annullate o le LRA che superano determinate durate per rilevare potenziali problemi in anticipo.

Esempi reali: LRA in azione

Vediamo un paio di scenari reali in cui MicroProfile LRA brilla:

Esempio 1: Sistema di prenotazione viaggi

Immagina un sistema di prenotazione viaggi in cui gli utenti possono prenotare voli, hotel e noleggi auto in un'unica transazione. Ecco come potresti strutturarlo usando LRA:


@Path("/booking")
public class BookingService {

    @Inject
    FlightService flightService;
    
    @Inject
    HotelService hotelService;
    
    @Inject
    CarRentalService carRentalService;

    @POST
    @Path("/create")
    @LRA(LRA.Type.REQUIRES_NEW)
    public Response createBooking(BookingRequest request) {
        flightService.bookFlight(request.getFlightDetails());
        hotelService.reserveRoom(request.getHotelDetails());
        carRentalService.rentCar(request.getCarDetails());
        return Response.ok().build();
    }

    @Compensate
    public Response compensateBooking(URI lraId) {
        flightService.cancelFlight(lraId);
        hotelService.cancelReservation(lraId);
        carRentalService.cancelRental(lraId);
        return Response.ok().build();
    }

    @Complete
    public Response completeBooking(URI lraId) {
        flightService.confirmFlight(lraId);
        hotelService.confirmReservation(lraId);
        carRentalService.confirmRental(lraId);
        return Response.ok().build();
    }
}

In questo esempio, se una parte della prenotazione fallisce (ad esempio, l'hotel è al completo), l'intera transazione viene annullata, garantendo che l'utente non finisca con prenotazioni parziali.

Esempio 2: Elaborazione ordini e-commerce

Ecco come un sistema di elaborazione ordini e-commerce potrebbe utilizzare LRA per gestire le complessità dell'evasione degli ordini:


@Path("/order")
public class OrderProcessingService {

    @Inject
    InventoryService inventoryService;
    
    @Inject
    PaymentService paymentService;
    
    @Inject
    ShippingService shippingService;
    
    @Inject
    NotificationService notificationService;

    @POST
    @Path("/process")
    @LRA(LRA.Type.REQUIRES_NEW, timeLimit = 30, timeUnit = ChronoUnit.MINUTES)
    public Response processOrder(Order order) {
        String orderId = order.getId();
        inventoryService.reserveItems(orderId, order.getItems());
        paymentService.processPayment(orderId, order.getTotalAmount());
        String trackingNumber = shippingService.createShipment(orderId, order.getShippingAddress());
        notificationService.sendOrderConfirmation(orderId, trackingNumber);
        return Response.ok().build();
    }

    @Compensate
    public Response compensateOrder(URI lraId) {
        String orderId = extractOrderId(lraId);
        inventoryService.releaseReservedItems(orderId);
        paymentService.refundPayment(orderId);
        shippingService.cancelShipment(orderId);
        notificationService.sendOrderCancellationNotice(orderId);
        return Response.ok().build();
    }

    @Complete
    public Response completeOrder(URI lraId) {
        String orderId = extractOrderId(lraId);
        inventoryService.confirmItemsShipped(orderId);
        paymentService.finalizePayment(orderId);
        shippingService.dispatchShipment(orderId);
        notificationService.sendShipmentDispatchedNotification(orderId);
        return Response.ok().build();
    }

    private String extractOrderId(URI lraId) {
        // Estrai l'ID dell'ordine dall'ID LRA
        // ...
    }
}

Questo esempio mostra come LRA possa gestire un flusso di elaborazione ordini complesso, garantendo che tutti i passaggi (gestione dell'inventario, elaborazione dei pagamenti, spedizione e notifica al cliente) siano coordinati e possano essere annullati se necessario.

Conclusione: Abbracciare l'armonia distribuita con LRA

MicroProfile LRA porta una ventata di aria fresca nel mondo delle transazioni distribuite. Fornisce un approccio standardizzato e basato su annotazioni per gestire azioni a lungo termine tra microservizi, trovando un equilibrio tra consistenza e le realtà dei sistemi distribuiti.

Punti chiave:

  • LRA abbraccia la consistenza eventuale, rendendola una scelta eccellente per le architetture a microservizi
  • L'approccio basato su annotazioni riduce il codice boilerplate e rende più facile ragionare sui confini delle transazioni
  • Il supporto integrato per la compensazione consente una gestione elegante dei fallimenti
  • L'integrazione con MicroProfile rende il monitoraggio e le metriche un gioco da ragazzi

Quando ti avventuri nel mondo delle transazioni distribuite, considera di dare una possibilità a MicroProfile LRA. Potrebbe essere proprio la salsa segreta che i tuoi microservizi stavano cercando!

"Nei sistemi distribuiti, la perfezione è nemica del bene. LRA ci aiuta a raggiungere una consistenza 'abbastanza buona' senza sacrificare scalabilità o prestazioni."

Ricorda, mentre LRA è potente, non è una soluzione universale. Considera sempre il tuo caso d'uso specifico e i requisiti quando scegli tra LRA, Sagas o altri pattern di transazioni distribuite.

Buona programmazione, e che le tue transazioni distribuite siano sempre a tuo favore!