Events and messaging

4 min read ·

Event-Driven Architecture: An Event Happened. Who Owns the Outcome?

An order is confirmed and several systems react. But what if a message is delayed, duplicated or never processed? The value and hidden cost of event-driven systems.

An order is confirmed. The warehouse should start picking, the customer should receive a notification, and analytics should record the sale. It looks like one flow. But should the order service know every participant by name and wait for them all to reply?

Event-driven architecture offers a different arrangement: the source announces a fact, and other systems react according to their own rules. That reduces direct dependencies. It also removes the comforting illusion that one synchronous call controls everything that follows.

An order system publishes an event that branches toward a warehouse, notification service and analytics
One system publishes a fact and independent consumers react. The illustration shows the direction of work, not a guarantee of immediate delivery, strict ordering or exactly-once processing.

An event is not an instruction to another team

The distinction matters. “Send this customer an email” is a command addressed to someone specific. “Order confirmed” is a fact about a business state that has already changed.

After that fact is published, notifications may send an email, inventory may begin fulfilment, and analytics may update its measures. The order system does not need to be rewritten every time the company adds another consumer.

That is the attraction: a loyalty service can start reacting to purchases without becoming another dependency in the checkout call chain. But if one step must succeed before the whole order can be considered complete, publishing an event alone is not enough. You may need explicit coordination or a Saga.

Independent services still share a customer outcome

It can be perfectly reasonable for an email service to be unavailable while checkout continues. The message can wait in a queue. The customer gets an email later, but the order itself remains valid.

Payment is different. If the company promises a completed purchase only after payment confirmation, it should not tell the customer everything is done on the strength of an unprocessed event.

Before choosing asynchronous communication, I would separate reactions that may happen later from steps that are essential to completing the transaction. Those groups need different guarantees and different product behaviour.

Messages can repeat, arrive late or arrive out of order

There is no universal distributed promise that every event arrives once, immediately and in the order everyone expects. Some brokers have stronger delivery or ordering features, but those guarantees have a scope and conditions.

If a handler sees the same payment event twice, it must not ship two parcels or credit loyalty points twice. Event identities, idempotent processing and a considered retry policy are part of the design, not optional infrastructure polish.

There is also a failure window between saving a business change and publishing its event. The database commit succeeds, the application crashes before sending the message, and consumers never hear about it. A Transactional Outbox can close that gap, though consumers still need to tolerate duplicates.

Who can tell whether an order actually finished?

With a synchronous request, failures are often immediately visible to the caller. In an event-driven flow, the order may be stored while its messages wait and downstream work silently falls behind. Everything looks “up” apart from the outcome customers are waiting for.

So the system needs correlation IDs, consumer-lag monitoring, failed-message handling, safe replay and a view of business process state. Installing a message broker does not tell you which order is stuck.

The same applies to ownership. Who maintains the event contract? Who warns consumers when the schema changes? Who takes responsibility if the producer publishes correctly but fulfilment never runs? Event-driven design is as much about agreements between teams as it is about transport.

When events are the expensive option

If a small application simply calls one internal function, inserting a queue between them may not make anyone's life better. It adds storage, retries, monitoring and delay to what used to be a straightforward call.

I would choose events where several independent consumers genuinely need to respond on their own schedules and should not block the system that owns the original fact. Before signing off, I would still ask to see the behaviour when an event is delayed, duplicated or missing — using a real order scenario, not just arrows on a slide.

Events are valuable when they let teams act independently without leaving the customer's result unowned. Removing direct calls is only the first half of the job.

The Transactional Outbox reference describes one important failure mode; the chosen broker's documentation defines its actual delivery guarantees.