Transaktionen & Konsistenz: Local, JTA, Saga, Outbox
Enterprise-Systeme scheitern häufig nicht am Code, sondern an falschen Konsistenzannahmen.
JTAOutboxSagaIdempotenz
In dieser Datei
Transaktionsarten
| Art | Beispiel | Grenze |
|---|---|---|
| Lokale DB-Transaktion | Order + Outbox in einer DB | Nur eine Datenbank |
| JTA/XA | DB + JMS in einer globalen TX | Komplex, teuer, nicht überall cloud-nativ |
| Saga | Order, Billing, Inventory separat | Eventually consistent |
| Outbox | DB-Commit und spätere Event-Publikation | Relay und Idempotenz nötig |
Outbox
Outbox speichert ein Event in derselben Transaktion wie den fachlichen Zustand. Ein separater Prozess veröffentlicht später zuverlässig.
Outbox Relay
public interface OutboxPort { // Pattern: Transactional Outbox Port
void append(DomainEvent event);
void appendAll(List<DomainEvent> events);
}
public final class OutboxRelay { // Pattern: Polling Publisher
private final OutboxStore store;
private final MessagePublisher publisher;
public void publishPending() {
for (OutboxMessage msg : store.lockNextBatch(100)) {
publisher.publish(msg.topic(), msg.payload(), msg.idempotencyKey());
store.markPublished(msg.id());
}
}
}
Saga
Eine Saga koordiniert mehrere lokale Transaktionen. Bei Fehlern wird nicht zurückgerollt wie in einer DB, sondern kompensiert.
Idempotenz
Jeder Consumer muss Mehrfachzustellung aushalten. Dafür braucht man Idempotency Keys, deduplizierende Tabellen oder fachliche natürliche Schlüssel.
Code
Saga mit Kompensation
public final class OrderSaga { // Pattern: Saga Orchestrator
public void on(OrderPlaced event) {
try {
billing.reserveInvoice(event.orderId());
inventory.commitReservation(event.orderId());
} catch (BillingUnavailable ex) {
inventory.releaseReservation(event.orderId()); // Kompensation
orders.markPaymentPending(event.orderId());
}
}
}
Warnung
Nicht jede verteilte Fachlogik braucht sofort Saga. Häufig reicht zuerst ein klarer Outbox-Prozess mit idempotenten Consumern.