Transactions and failures
Modular Hexagonal DDD does not promise a distributed two-phase commit across modules. Each Use Case owns a clear consistency boundary.
Related: ACL · Event delivery · Orchestration.
Ownership rule
| Rule | Detail |
|---|---|
| One Use Case, one primary write set | Prefer a single Shared DatabaseTransactionInterface (or host unit of work) around this module’s writes |
| Peers are remote | Treat sync ACL like an external call — even when in-process today |
| No cross-module DB transaction | Do not open one SQL transaction that writes Ordering and Warehouse tables together |
Sync ACL — fail before you commit
When the publisher needs a peer answer before it is safe to commit:
Prefer: run read/validate ACL calls before opening the write transaction (or before the first irreversible side effect).
If you must call ACL inside a transaction (e.g. reserve then write):
- Keep the peer call short.
- On peer failure → roll back this module’s transaction.
- Never leave “Ordering committed + Warehouse half-reserved” without a defined compensation (see below).
Failure modes
| Situation | Expected behaviour |
|---|---|
| Peer ACL timeout / 5xx | Fail the Use Case; caller retries or shows error — do not pretend success |
| Peer says “no” (business) | Domain/Application policy: reject or alternate path — still no silent partial write |
| Peer succeeded, local commit fails | Compensate peer if the peer call was a write (release reservation); prefer peer APIs that are confirm/cancel capable |
| Local commit succeeded, async consumer fails | Eventual consistency + retry / dead-letter (event delivery) |
Compensating actions
For multi-step sync writes across modules, prefer explicit steps:
- Reserve (peer write ACL) → returns a reservation code
- Commit local aggregate referencing that code
- On local failure → Release (peer write ACL)
- On success → later Confirm via Event or second ACL
That is still ACL + Events — not a shared transaction. Longer chains → orchestration.
What not to do
| Anti-pattern | Why |
|---|---|
| Single DB transaction spanning two modules’ tables | Hidden deploy/runtime coupling; fails the HTTP-replace mental model |
| Catch peer errors and commit anyway “to fix later” without an outbox/event | Silent inconsistency |
| UI retries that double-create without idempotency keys | Duplicate aggregates |
Next: orchestration · anti-patterns.