Events, audit & workflows
Two related problems that hand-built systems usually get wrong: publishing an event for a write that never actually committed (or the reverse — committing without ever publishing), and multi-step operations across services that fail halfway through and leave inconsistent state behind.
The transactional outbox
Domain writes stage their event in the same database transaction as the write itself. A separate publisher ships staged events to Kafka after the transaction commits — so an event is never published for a write that got rolled back, and a committed write is never silently missing its event.
@Transactional
void createLead(CreateLeadRequest req) {
Lead lead = leadRepo.save(new Lead(req));
// Written to the SAME transaction as the lead itself.
outbox.publish(new LeadCreatedEvent(lead.getId(), ...));
// If this transaction rolls back, the event was never staged.
// A background publisher ships staged events to Kafka after commit.
}Immutable audit trail
Those same events feed a searchable, immutable audit store — the basis for “who changed what, when” compliance questions, and for downstream automation that reacts to what happened elsewhere in the platform without polling.
Sagas for cross-service workflows
Multi-step operations that touch more than one service — onboarding a tenant, changing a subscription, provisioning a team — run as orchestrated sagas with compensation logic, so a failure partway through unwinds cleanly instead of leaving, say, a tenant created but its billing plan never attached.
This is the same mechanism proven live in Case Study #001 — the order-confirmation email that fired after a real checkout came from an event on this same outbox pipeline, not a special case.