Stel je voor dat je een e-commerce platform bouwt met microservices voor voorraadbeheer, betalingen en verzending. Een klant plaatst een bestelling en plotseling sta je voor de eeuwenoude vraag: hoe zorg je ervoor dat al deze services goed samenwerken zonder dat je er grijze haren van krijgt?

Traditionele ACID-transacties zijn geweldig voor monolieten, maar ze schieten tekort in de wereld van microservices. Het is alsof je een moker gebruikt om een noot te kraken - overkill en waarschijnlijk meer problemen veroorzakend dan oplossend. Hier komt LRA om de dag te redden.

Waarom ACID niet voldoende is:

  • Strakke koppeling tussen services (een grote no-go in microservices)
  • Prestatieknelpunten door vergrendeling
  • Schaalbaarheidsproblemen naarmate je systeem groeit

LRA kiest een andere benadering. In plaats van atomische transacties over services af te dwingen, omarmt het het model van uiteindelijke consistentie. Het is als het coördineren van een dansroutine waarbij elke danser (service) zijn rol kent en weet hoe te herstellen als iemand op hun tenen stapt.

MicroProfile LRA

Dus, wat is MicroProfile LRA precies? Zie het als een choreograaf voor je microservicesballet. Het biedt een gestandaardiseerde manier om langlopende, gedistribueerde operaties te beheren die meerdere services omvatten.

Belangrijke Concepten:

  • Langlopende Acties: Operaties die seconden, minuten of zelfs uren kunnen duren
  • Compensatie: De mogelijkheid om acties ongedaan te maken als het misgaat
  • Uiteindelijke Consistentie: Het accepteren dat consistentie tussen services tijd kost

LRA probeert geen onmiddellijke consistentie af te dwingen. In plaats daarvan biedt het je tools om de levenscyclus van deze langlopende operaties te beheren en ervoor te zorgen dat je systeem uiteindelijk een consistente staat bereikt.

LRA Instellen: Een Stapsgewijze Gids

Klaar om wat LRA-magie aan je project toe te voegen? Laten we het instellen doorlopen:

1. Voeg de MicroProfile LRA-afhankelijkheid toe

Allereerst moet je de MicroProfile LRA-afhankelijkheid aan je project toevoegen. Als je Maven gebruikt, voeg dit dan toe aan je pom.xml:


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

2. Configureer je LRA-coördinator

Je hebt een LRA-coördinator nodig om je LRA's te beheren. Dit kan een aparte service zijn of deel uitmaken van je bestaande infrastructuur. Hier is een eenvoudige configuratie met Quarkus:


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

3. Annoteer je methoden

Nu komt het leuke gedeelte - het annoteren van je methoden om deel te nemen aan LRA's. Hier is een eenvoudig voorbeeld:


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

    @POST
    @Path("/create")
    @LRA(LRA.Type.REQUIRED)
    public Response createOrder(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId) {
        // Je logica voor het maken van een bestelling hier
        return Response.ok().build();
    }

    @PUT
    @Path("/complete")
    @Complete
    public Response completeOrder(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId) {
        // Logica om de bestelling te voltooien
        return Response.ok().build();
    }

    @PUT
    @Path("/compensate")
    @Compensate
    public Response compensateOrder(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId) {
        // Logica om de bestelling te compenseren (annuleren)
        return Response.ok().build();
    }
}

In dit voorbeeld start of sluit createOrder zich aan bij een LRA, wordt completeOrder aangeroepen wanneer de LRA succesvol wordt voltooid, en wordt compensateOrder aangeroepen als de LRA moet worden teruggedraaid.

LRA Annotaties: Je Nieuwe Beste Vrienden

LRA komt met een reeks annotaties die het beheren van langlopende acties eenvoudig maken. Laten we de belangrijkste doornemen:

@LRA

Dit is de ster van de show. Gebruik het om de reikwijdte van je LRA te definiëren:


@LRA(LRA.Type.REQUIRED)
public Response doSomething() {
    // Deze methode zal altijd binnen een LRA draaien
}

De Type parameter kan zijn:

  • REQUIRED: Sluit aan bij een bestaande LRA of maak een nieuwe
  • REQUIRES_NEW: Maak altijd een nieuwe LRA
  • MANDATORY: Moet worden aangeroepen binnen een bestaande LRA
  • SUPPORTS: Gebruik een LRA indien aanwezig, anders zonder
  • NOT_SUPPORTED: Draai zonder een LRA, zelfs als er een bestaat

@Compensate

Dit is je vangnet. Gebruik het om te definiëren wat er moet gebeuren als het misgaat:


@Compensate
public Response undoStuff(URI lraId) {
    // Opruimlogica hier
    return Response.ok().build();
}

@Complete

Het gelukkige pad. Deze methode wordt aangeroepen wanneer je LRA succesvol wordt voltooid:


@Complete
public Response finalizeStuff(URI lraId) {
    // Finalisatielogica hier
    return Response.ok().build();
}

Acties Coördineren: De LRA Dans

Nu we onze annotaties op hun plaats hebben, laten we zien hoe LRA acties coördineert over services. Stel je voor dat we een online boekwinkel bouwen met aparte services voor voorraadbeheer, betalingen en verzending.

De Bestelstroom:


@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) {
        // Stap 1: Reserveer voorraad
        inventoryService.reserveBooks(order.getBooks());
        
        // Stap 2: Verwerk betaling
        paymentService.processPayment(order.getTotal());
        
        // Stap 3: Regel verzending
        shippingService.arrangeShipment(order.getShippingDetails());
        
        return Response.ok().build();
    }

    @Compensate
    public Response compensateOrder(URI lraId) {
        // Als er iets misgaat, wordt dit aangeroepen om alles ongedaan te maken
        inventoryService.releaseReservation(lraId);
        paymentService.refundPayment(lraId);
        shippingService.cancelShipment(lraId);
        return Response.ok().build();
    }

    @Complete
    public Response completeOrder(URI lraId) {
        // Dit wordt aangeroepen wanneer alles slaagt
        inventoryService.confirmReservation(lraId);
        paymentService.confirmPayment(lraId);
        shippingService.confirmShipment(lraId);
        return Response.ok().build();
    }
}

In dit voorbeeld, als een stap mislukt (bijv. betaling gaat niet door), wordt de @Compensate methode aangeroepen, waardoor alle voorgaande stappen ongedaan worden gemaakt. Als alles slaagt, finaliseert de @Complete methode de bestelling.

Timeouts en Annuleringsbeleid: Je LRA's Onder Controle Houden

LRA's zijn van nature langlopend, maar soms verandert "langlopend" in "eindeloos". Om te voorkomen dat je LRA's uit de hand lopen, biedt MicroProfile LRA mechanismen voor timeouts en annulering.

Timeouts Instellen

Je kunt een timeout instellen voor je LRA met de timeLimit parameter van de @LRA annotatie:


@LRA(value = LRA.Type.REQUIRED, timeLimit = 10, timeUnit = ChronoUnit.MINUTES)
public Response longRunningOperation() {
    // Deze LRA zal automatisch verlopen na 10 minuten
    // ...
}

Als de LRA niet binnen de gespecificeerde tijd voltooid is, wordt deze automatisch geannuleerd en wordt de @Compensate methode aangeroepen.

Handmatige Annulering

Soms moet je een LRA handmatig annuleren. Je kunt dit doen door de LRAClient te injecteren en de cancel methode aan te roepen:


@Inject
LRAClient lraClient;

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

LRA vs. Sagas: Je Wapen Kiezen

Op dit punt denk je misschien: "Wacht eens even, dit klinkt veel als het Saga-patroon!" Je hebt gelijk. LRA's en Sagas zijn als neven in de familie van gedistribueerde transacties. Laten we de overeenkomsten en verschillen bekijken:

Overeenkomsten:

  • Beide behandelen langlopende, gedistribueerde transacties
  • Beide gebruiken compensatie om gedeeltelijk werk ongedaan te maken
  • Beide streven naar uiteindelijke consistentie

Verschillen:

  • LRA is een gestandaardiseerde specificatie, terwijl Saga een patroon is
  • LRA biedt ingebouwde ondersteuning via annotaties, waardoor het eenvoudiger te implementeren is
  • Sagas gebruiken meestal evenementen voor coördinatie, terwijl LRA HTTP-headers gebruikt

Dus, wanneer moet je LRA boven Sagas gebruiken?

  • Als je een MicroProfile-compatibel framework gebruikt (zoals Quarkus of Open Liberty)
  • Wanneer je een gestandaardiseerde aanpak wilt met minder boilerplate code
  • Als je de voorkeur geeft aan een meer declaratieve stijl met annotaties

Aan de andere kant kunnen Sagas een betere keuze zijn als:

  • Je meer gedetailleerde controle over het compensatieproces nodig hebt
  • Je systeem sterk evenementgestuurd is
  • Je geen MicroProfile-compatibel framework gebruikt

Het Monitoren en Beheren van LRA Levenscycli

Nu we onze LRA's aan de gang hebben, hoe houden we ze in de gaten? Het monitoren van LRA's is cruciaal voor het begrijpen van de gezondheid en prestaties van je gedistribueerde transacties.

Te Volgen Statistieken

MicroProfile LRA biedt verschillende statistieken out-of-the-box:

  • lra_started: Aantal gestarte LRA's
  • lra_completed: Aantal succesvol voltooide LRA's
  • lra_cancelled: Aantal geannuleerde LRA's
  • lra_duration: Duur van LRA's

Je kunt deze statistieken blootstellen met MicroProfile Metrics. Hier is hoe je het in Quarkus zou kunnen instellen:


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

Integratie met Monitoring Tools

Zodra je je statistieken hebt blootgesteld, kun je ze integreren met populaire monitoringtools. Hier is een snel voorbeeld van hoe je een Prometheus scrape-configuratie zou kunnen instellen:


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

En een eenvoudige Grafana-dashboardquery om LRA-duur te visualiseren:


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

Logging en Waarschuwingen

Vergeet niet om goede logging voor je LRA's in te stellen. Hier is een eenvoudig voorbeeld met SLF4J:


@Inject
Logger logger;

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

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

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

Stel waarschuwingen in voor geannuleerde LRA's of LRA's die bepaalde duur overschrijden om potentiële problemen vroegtijdig te detecteren.

Praktijkvoorbeelden: LRA in Actie

Laten we eens kijken naar een paar praktijkvoorbeelden waar MicroProfile LRA uitblinkt:

Voorbeeld 1: Reissysteem voor Boekingen

Stel je een reissysteem voor waar gebruikers vluchten, hotels en autoverhuur in één transactie kunnen boeken. Hier is hoe je het zou kunnen structureren met 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 dit voorbeeld, als een deel van de boeking mislukt (bijv. het hotel is volgeboekt), wordt de hele transactie teruggedraaid, zodat de gebruiker niet met gedeeltelijke boekingen blijft zitten.

Voorbeeld 2: E-commerce Bestelverwerking

Hier is hoe een e-commerce bestelverwerkingssysteem LRA zou kunnen gebruiken om de complexiteit van orderafhandeling aan te pakken:


@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) {
        // Haal order ID uit LRA ID
        // ...
    }
}

Dit voorbeeld laat zien hoe LRA een complexe bestelverwerkingsstroom kan beheren, waarbij ervoor wordt gezorgd dat alle stappen (voorraadbeheer, betalingsverwerking, verzending en klantmelding) gecoördineerd worden en indien nodig kunnen worden teruggedraaid.

Conclusie: Omarm Gedistribueerde Harmonie met LRA

MicroProfile LRA brengt een frisse wind in de wereld van gedistribueerde transacties. Het biedt een gestandaardiseerde, annotatiegestuurde aanpak voor het beheren van langlopende acties over microservices, waarbij een balans wordt gevonden tussen consistentie en de realiteit van gedistribueerde systemen.

Belangrijke punten:

  • LRA omarmt uiteindelijke consistentie, waardoor het een goede keuze is voor microservicesarchitecturen
  • De annotatiegebaseerde aanpak vermindert boilerplate en maakt het eenvoudiger om over transactiegrenzen na te denken
  • Ingebouwde ondersteuning voor compensatie maakt een gracieuze afhandeling van fouten mogelijk
  • Integratie met MicroProfile maakt monitoring en statistieken eenvoudig

Als je de wereld van gedistribueerde transacties betreedt, overweeg dan om MicroProfile LRA een kans te geven. Het zou wel eens de geheime saus kunnen zijn waar je microservices naar verlangen!

"In gedistribueerde systemen is perfect de vijand van goed. LRA helpt ons om 'goed genoeg' consistentie te bereiken zonder in te boeten op schaalbaarheid of prestaties."

Onthoud, hoewel LRA krachtig is, is het geen wondermiddel. Overweeg altijd je specifieke use case en vereisten bij het kiezen tussen LRA, Sagas of andere gedistribueerde transactiepatronen.

Veel programmeerplezier, en moge je gedistribueerde transacties altijd in je voordeel zijn!