In monolithischen Anwendungen wird i. A. das Transaktionssystem genutzt, um einen konsistenten Datenzustand zu garantieren. Nehmen wir bspw. an, dass eine Reisebuchungsanwendung in einem Geschäftsprozess einen Flug und eine Hotelübernachtung buchen kann, so lässt sich mit Hilfe einer den Prozess umschließenden Transaktion sicherstellen, dass die Buchung komplett oder – im Fehlerfall – gar nicht ausgeführt wird.
Teilen wir die Anwendung nun auf in zwei Microservices für Flüge, Hotels sowie einen Service für die Reisebuchung, so stehen uns systemweite Transaktionen nicht mehr zur Verfügung. Um dennoch einen konsistenten Buchungsstand zu erreichen, kann man das Saga Pattern einsetzen. Grob gesagt werden darin die Teiloperationen zwar direkt ausgeführt, dabei aber Kompensationsfunktionen registriert, die im Fehlerfall einen konsistenten Systemzustand wieder herstellen. So würde bspw. eine Hotelübernachtung reserviert, noch bevor der ebenfalls gewünschte Flug gebucht wird. Sollte das dann fehlschlagen, wird die Hotelbuchung storniert. Anders als bei transaktionalen Prozessen ist der Systemzustand beim Saga Pattern nur schlussendlich konsistent (“eventually consistent”).
MicroProfile LRA (“Long Running Actions”) ist eine konkrete Ausprägung des Saga Patterns als MicroProfile Specification. Der Fokus liegt i. W. auf Webservices, d. h. es wird angenommen, dass die Microservices ihre Dienste mittels Jakarta RESTful Webservices (aka JAX-RS) anbieten und dass sie mit anderen Services über MicroProfile REST Clients kommunizieren.
Eine LRA wird dabei im einfachsten Fall mit einer REST Method gestartet, wozu sie mit der Annotation @LRA
versehen wird:
@Path("xyz")
public class TravelBookingResource {
@PUT
@Path("book/{whatToBook}")
@LRA(LRA.Type.REQUIRES_NEW)
public void book(@PathParam("whatToBook") String whatToBook, @HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) URI lraId) {
logger.debugf("Starting LRA %s", lraId);
@LRA erhält als Parameter u. a. einen Typ ähnlich den von der deklarativen Transaktionssteuerung bekannten Modi (REQUIRED
, REQUIRES_NEW
, MANDATORY
etc.). Jede LRA wird über eine URI repräsentiert, die aus einem HTTP Header übernommen werden kann.
Ist in der REST Resource nun ein weitere PUT
-Methode vorhanden und mit @Compensate
annotiert, so wird diese als Kompensationsmethode in der LRA registriert. Sollte die LRA fehlerhaft enden, z. B. dadurch, dass die steuernde Methode eine Exception auswirft, werden alle registrierten Kompensationsmethoden aufgerufen.
Das hier beschriebene Szenario skizziert nur einen einfachen Fall. Es gibt darüber hinaus noch Completion Methods, Notifications, Nested LRAs etc. Wir werden einiges davon in einem der nächsten Treffen unseres Expertenkreises vorstellen – stay tuned!