Enterprise Architecture · Enterprise · Topic 2 of 5
Saga Choreography with Kafka
In a choreographed saga each service reacts to events and emits its own, with no central orchestrator. This sketch shows the flow, not a production recipe: each service here writes to its database and then sends to Kafka, which is the dual-write problem the outbox pattern exists to solve.
// === ORDER SERVICE ===
@Service
public class OrderService {
@Transactional
public void createOrder(CreateOrderRequest req) {
Order order = orderRepo.save(Order.pending(req));
// Starts the saga: the payment service listens to this
kafkaTemplate.send("orders.order.created",
order.getId(), new OrderCreatedEvent(order));
}
// Compensating action if payment fails
@KafkaListener(topics = "payments.payment.failed")
@Transactional
public void onPaymentFailed(PaymentFailedEvent event) {
Order order = orderRepo.findById(event.getOrderId()).orElseThrow();
order.cancel("Payment failed: " + event.getReason());
orderRepo.save(order);
kafkaTemplate.send("orders.order.cancelled", order.getId(), new OrderCancelledEvent(order));
}
@KafkaListener(topics = "payments.payment.completed")
@Transactional
public void onPaymentCompleted(PaymentCompletedEvent event) {
Order order = orderRepo.findById(event.getOrderId()).orElseThrow();
order.confirmPayment();
orderRepo.save(order);
// Next saga step: inventory reservation
kafkaTemplate.send("inventory.reservation.requested",
order.getId(), new ReservationRequest(order));
}
}
// === PAYMENT SERVICE ===
@Service
public class PaymentService {
@KafkaListener(topics = "orders.order.created")
public void onOrderCreated(OrderCreatedEvent event) {
try {
PaymentResult result = paymentGateway.charge(event.getAmount(), event.getCustomerId());
kafkaTemplate.send("payments.payment.completed",
event.getOrderId(), new PaymentCompletedEvent(event, result));
} catch (PaymentException e) {
kafkaTemplate.send("payments.payment.failed",
event.getOrderId(), new PaymentFailedEvent(event, e.getMessage()));
}
}
}Sources
- Reliable microservices data exchange with the outbox pattern (Debezium) (opens in a new tab)
- Tips, tricks and examples (Spring for Apache Kafka) (opens in a new tab)
- Transactions (Spring for Apache Kafka) (opens in a new tab)
Checked against Spring for Apache Kafka 4.1.1, Spring Boot 4.1.1 and Kafka 4.2 documentation on 11 October 2026. Draft: not yet reviewed by a Kafka specialist, and releases move on, so confirm against the current documentation before you act on it.