Add the saga module: Saga Pattern in Spring Boot, orchestration vs choreography with Kafka
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# saga
|
||||
|
||||
Companion code for *Saga Pattern in Spring Boot: Orchestration vs Choreography with Kafka* on
|
||||
[ankurm.com](https://ankurm.com).
|
||||
|
||||
The same order/payment/inventory saga, implemented twice against a real (embedded) Kafka
|
||||
broker: once as **choreography** (every participant reacts to the previous step's event and
|
||||
decides for itself what happens next) and once as **orchestration** (one class issues commands
|
||||
and makes every decision; participants only ever answer a command). Both versions are driven
|
||||
through the same failure -- insufficient inventory -- so the compensating transaction that
|
||||
undoes the reserved payment can be compared side by side. A fourth test demonstrates the
|
||||
idempotent-consumer problem concretely: the same command, redelivered, reserving stock only
|
||||
once.
|
||||
|
||||
## Versions
|
||||
|
||||
| Artifact | Version |
|
||||
|---|---|
|
||||
| Spring Boot | 4.1.1 (GA; verified against Maven Central's `maven-metadata.xml` -- `4.2.0-M2` is the newest entry there but is a milestone, not GA) |
|
||||
| Spring Kafka | 4.1.1, managed by the Spring Boot BOM |
|
||||
| JDK | 25 (LTS) |
|
||||
| H2 | runtime, in-memory, for the demo only |
|
||||
|
||||
## Quickstart
|
||||
|
||||
```
|
||||
mvn -pl saga test -Dtest=ChoreographyHappyPathTest
|
||||
mvn -pl saga test -Dtest=ChoreographyCompensationTest
|
||||
mvn -pl saga test -Dtest=OrchestrationSagaTest
|
||||
mvn -pl saga test -Dtest=IdempotentConsumerTest
|
||||
```
|
||||
|
||||
Each test class starts its own embedded Kafka broker and its own isolated in-memory H2
|
||||
database (a random schema name per test, via `@DynamicPropertySource`), so running them
|
||||
individually or together (`mvn -pl saga test`) gives the same results.
|
||||
|
||||
## What each class is
|
||||
|
||||
| Class | Role |
|
||||
|---|---|
|
||||
| `choreography.ChoreographySagaStarter` | Creates the order, publishes `OrderCreated`, and does nothing else -- the only choreography class that "starts" anything |
|
||||
| `choreography.PaymentChoreographyListener` | Reserves payment on `OrderCreated`; refunds it on `InventoryRejected` (the compensating transaction) |
|
||||
| `choreography.InventoryChoreographyListener` | The saga's one failure trigger: reacts to `PaymentReserved`, decides stock is or isn't available, publishes accordingly |
|
||||
| `choreography.OrderChoreographyListener` | Confirms or cancels the order; never decides anything, only reacts |
|
||||
| `orchestration.OrderSagaOrchestrator` | Every saga decision lives here: what to do after each reply, including issuing the compensating `RefundPaymentCommand` |
|
||||
| `orchestration.PaymentOrchestrationHandler` / `InventoryOrchestrationHandler` | Carry out exactly one command each and reply; never decide what happens next |
|
||||
| `idempotency.IdempotencyGuard` | `claim(commandId)` -- true the first time a command id is seen, false on every redelivery, backed by a real UNIQUE constraint |
|
||||
| `inventory.InventoryService` | In-memory stock only -- this module is about saga coordination, not inventory persistence |
|
||||
|
||||
## Captured output (`output/`)
|
||||
|
||||
| File | What it captures |
|
||||
|---|---|
|
||||
| `00-choreography-happy-path.txt` | Enough stock: payment reserved, inventory reserved, order confirmed, with no single class aware of the whole sequence |
|
||||
| `01-choreography-compensation.txt` | Not enough stock: inventory rejects, payment refunds itself in response, order ends cancelled |
|
||||
| `02-orchestration-saga.txt` | Both scenarios again, driven by `OrderSagaOrchestrator` instead -- same outcomes, one class making every decision |
|
||||
| `03-idempotent-consumer-duplicate.txt` | The same `ReserveInventoryCommand`, same commandId, delivered twice: one processed-command row, one stock reservation, one reply |
|
||||
|
||||
No `docs/` chapter directory in this repository -- the intermediate and reference-depth
|
||||
material that would normally live there is in accordion sections inside the WordPress post
|
||||
itself.
|
||||
Reference in New Issue
Block a user