55 lines
3.2 KiB
Markdown
55 lines
3.2 KiB
Markdown
# outbox
|
|
|
|
Companion code for *[Transactional Outbox with the Spring Modulith Event Publication
|
|
Registry](https://ankurm.com)* on [ankurm.com](https://ankurm.com).
|
|
|
|
A single `billing` module demonstrating the dual-write problem, Spring Modulith's event
|
|
publication registry acting as a real transactional outbox, externalizing events to a
|
|
real (embedded) Kafka broker via `spring-modulith-events-kafka`, and replaying an
|
|
incomplete publication with `IncompleteEventPublications`. Every number in the
|
|
companion post traces to `output/`, in order.
|
|
|
|
## Versions
|
|
|
|
| Artifact | Version |
|
|
|---|---|
|
|
| Spring Boot | 4.1.1 |
|
|
| Spring Modulith | 2.1.1 (GA; verified against Maven Central's `maven-metadata.xml`) |
|
|
| spring-modulith-events-kafka | 2.1.1 |
|
|
| spring-kafka / spring-kafka-test | 4.1.1 (managed by the Spring Boot 4.1.1 BOM; `kafka-clients` 4.2.1) |
|
|
| JDK | 25 (LTS) |
|
|
| H2 | runtime, in-memory, for the demo only |
|
|
|
|
## Quickstart
|
|
|
|
```
|
|
mvn -pl outbox -am test -Dtest=DualWriteProblemTest
|
|
mvn -pl outbox -am test -Dtest=RegistrySafetyNetTest
|
|
mvn -pl outbox -am test -Dtest=ReplayIncompletePublicationsTest
|
|
```
|
|
|
|
Run separately on purpose -- `RegistrySafetyNetTest` starts a real embedded Kafka
|
|
broker (`@EmbeddedKafka`), which takes several seconds and is unrelated to what the
|
|
other two tests are checking.
|
|
|
|
## What each class is
|
|
|
|
| Class | Role |
|
|
|---|---|
|
|
| `billing.BillingManagement` | The outbox-safe way to issue an invoice: one local transaction, one `Invoice` row, one event publication registry row |
|
|
| `billing.NaiveBillingService` | The "before" picture -- a separate, uncoordinated `kafka.send()` call after the database write already committed. Never wired as the module's real API; only ever called directly from `DualWriteProblemTest` |
|
|
| `billing.InvoiceIssued` | `@Externalized("invoices::#{#this.invoiceId}")` -- routes to the `invoices` Kafka topic, keyed by the invoice id |
|
|
| `billing.FlakyAuditListener` (test-only) | Throws on its first invocation, succeeds after -- stands in for any downstream dependency being briefly unavailable, without needing to actually break a running broker mid-test |
|
|
|
|
## Captured output (`output/`)
|
|
|
|
| File | What it captures |
|
|
|---|---|
|
|
| `00-dual-write-problem.txt` | The invoice committed, Kafka never received either message, and a real stack trace showing `KafkaTemplate.send()` throwing *synchronously* when the broker is totally unreachable -- narrower than, and a correction to, the usual "fire-and-forget send() fails silently" claim |
|
|
| `01-registry-safety-net.txt` | The real production path: a real embedded Kafka broker receiving the real message, and the registry moving both listeners' rows into `EVENT_PUBLICATION_ARCHIVE` (`completion-mode=ARCHIVE` is turned on for this module -- the previous post in this series left it commented out) |
|
|
| `02-replay-incomplete-publications.txt` | A listener failing once, the registry leaving its row incomplete, and `IncompleteEventPublications.resubmitIncompletePublicationsOlderThan(Duration)` -- a real method, verified against the compiled `events-api` jar -- redelivering it successfully |
|
|
|
|
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.
|