Engineering field note

Choosing Boundaries in Event-Driven Systems

How to decide what should become an event, where ownership belongs, and how to avoid accidental coupling.

Event-driven architecture is often introduced as a way to decouple services. That benefit is real, but a message broker does not remove coupling by itself. It changes where coupling appears: in event contracts, delivery expectations, ownership, and operational behaviour.

The most important design decision is therefore not which broker to use. It is where the business boundary belongs.

Publish facts, not instructions

An event should usually describe something that has already happened:

  • OrderConfirmed
  • EmployeeProfileUpdated
  • DeviceCapacityThresholdReached

These names communicate facts that multiple consumers can interpret independently. A message such as UpdateReportingDatabase tells another service how to implement a reaction and creates tighter process coupling.

Commands are still useful when one component explicitly requests work from another, but they should be recognised as commands rather than disguised as events.

Keep ownership unambiguous

Every business concept should have a clear source of truth. The service that owns an entity publishes changes to it; consumers build their own views without writing back into the owner’s data model.

This separation becomes difficult when events simply expose internal database rows. Storage-shaped events leak implementation details and make schema changes expensive. Prefer contracts based on stable business meaning, with explicit versions when compatibility cannot be preserved.

Design for repeated delivery

Most practical event systems provide at-least-once delivery. A consumer may receive the same message more than once because acknowledgements and processing do not complete atomically.

Consumers should therefore be idempotent. Common approaches include recording processed event identifiers, using naturally idempotent upserts, or applying a unique constraint around the business operation. The correct choice depends on retention, throughput, and how costly duplicate effects would be.

Make ordering requirements local

Global ordering is expensive and frequently unnecessary. Ask which events truly require relative ordering and identify the smallest key that provides it. Ordering all employee updates by employee identifier, for example, is more scalable than forcing every event through one global sequence.

Consumers should also be prepared for missing context. An event may arrive after a newer state has already been processed, or a replay may intentionally rebuild a projection from history.

Treat operations as part of the architecture

An event flow is not complete without answers for:

  • Schema compatibility and ownership
  • Retry and dead-letter behaviour
  • Consumer lag and alerting
  • Replay procedures
  • Sensitive-data handling
  • Correlation across asynchronous steps

These concerns determine whether the system remains understandable after deployment. A clean architecture diagram is useful, but the operational contract is what makes the design dependable.

Good event-driven systems create autonomy at meaningful business boundaries. They do not turn every function call into a message; they use asynchronous communication where independent ownership, resilience, or timing genuinely benefits from it.

Back to the journal