Order-Saga
What it does
Order-Saga is an order-processing system split across five independent microservices — order, inventory, payment, shipping, and notification — that coordinate entirely through events on a Kafka bus. There is no central orchestrator telling each service what to do. Instead, each service reacts to the events it cares about and publishes its own result event, building a saga that either reaches a COMPLETED state or unwinds itself through automatic compensation when something fails.
How it works
How five services coordinate without an orchestrator
When a request arrives at the Gateway, it is forwarded to the Order service, which creates an order record in PENDING state and publishes an order.created event to Kafka. The Inventory service picks up that event, checks and reserves stock, then publishes inventory.reserved. Payment receives that same event and calls an external mock payment gateway. A successful response produces payment.succeeded, which triggers both Shipping and the Order service — Shipping creates a shipment and publishes shipment.created, which notifies both the Order service (COMPLETED) and the Notification service. No service ever calls another service directly; every handoff is a Kafka event.
What happens when a payment fails
If the payment gateway returns a failure, Payment publishes payment.failed to all three relevant topics. Inventory receives this and releases the previously reserved stock — the compensation step. Order receives the same event and moves to FAILED after passing through COMPENSATING. Notification receives payment.failed and sends a failure email. The entire compensation chain is driven by the same event-reacting model as the happy path; no extra orchestration layer is needed.
Idempotent consumers and optimistic locking
Kafka delivers messages at least once, so each service records the ID of every event it has already processed in a ProcessedEvent table. A duplicate delivery is detected and silently discarded rather than causing a double-booking. The Product entity in Inventory uses a JPA @Version field for optimistic locking, so two simultaneous reservation requests cannot both succeed against the same stock without one retrying.
Design decisions
- Choreography over orchestration: no saga orchestrator process exists, which eliminates a single point of failure and keeps each service independently deployable.
- One PostgreSQL database per service: each service owns its data and schema, with no shared tables or cross-service joins.
- OpenFeign clients target mock providers for payment, shipping, and email, making it easy to simulate failures without modifying business logic.
- The Eureka service registry is used for client-side load balancing rather than hard-coded addresses, enabling the Gateway to route by service name.
- Kafka UI and the Eureka dashboard provide operational visibility without any custom monitoring code.
Stack
| Layer | Technology |
|---|---|
| Framework | Spring Boot 3.4.3 |
| Language | Java 17 |
| Messaging | Apache Kafka 7.5.0 |
| Database | PostgreSQL 15 (one per service) |
| Spring | Spring Cloud Eureka |
| Spring | Spring Cloud Gateway |
| Spring | Spring Cloud OpenFeign |
| Spring | Spring Cloud 2024.0.1 |
| Spring | Spring Data JPA |
| Infra | Maven |
| Infra | Docker |
| Infra | Docker Compose |
| Messaging | Kafka UI |
| Library | Zookeeper |