Stackbook Logo
messagingestablished · medium operational burden

Domain Event

Also known as: domain-events, business-event

Intent

Capture something meaningful that happened in the domain as an immutable fact that other parts of the system can react to.

Problem

Business operations have side effects in other modules, such as sending email or updating inventory. Calling those modules directly couples the core domain to everything that cares about the change.

Forces

  • •Side effects should not bloat core domain methods
  • •Handlers must be added without modifying the producer
  • •Events must not be lost if the transaction commits
  • •Ordering and duplication need handling

Solution

Overview

A domain event states a fact: it already happened and cannot be undone. Names use past tense and business language, for example InvoicePaid rather than UpdateInvoiceStatus. Events differ from commands, which ask for something to happen and may be rejected.

How it works

The aggregate raises an event while enforcing its rules. The application layer saves the aggregate and then dispatches the collected events.

class Order:
    def place(self):
        self.status = "PLACED"
        self.events.append(OrderPlaced(self.id, self.total))

# after commit
for e in order.pull_events():
    bus.publish(e)
Order.place() -> OrderPlaced -> [Email handler]
                             -> [Inventory handler]
                             -> [Outbox -> Broker -> other contexts]

Reliable delivery to other services needs the transactional outbox: write the event into an outbox table in the same transaction as the state change, then a relay publishes it.

Pitfalls

  • Dispatching before commit, so handlers see state that later rolls back.
  • Putting too little data in events, forcing consumers to call back; or too much, leaking the internal model.
  • Non-idempotent handlers under at-least-once delivery.
  • Changing event shape without versioning.
  • Using events for in-process logic that needs a synchronous answer.

Key takeaways

  • Events are immutable facts in past tense.
  • Dispatch only after a successful commit, using an outbox for reliability.
  • Handlers must be idempotent.
  • Version event schemas from day one.

✓ When to Use

  • •Domain logic triggers independent side effects
  • •Decoupling aggregates or bounded contexts
  • •Building audit trails or event-driven integrations

✗ When Not to Use

  • •Simple flows where a direct call is clearer
  • •When strict immediate consistency across handlers is required
  • •Teams lacking tooling to trace asynchronous flows

Pros

  • +Decouples producers from consumers
  • +Makes domain language explicit
  • +Enables eventual consistency between aggregates

Cons

  • −Control flow becomes harder to follow
  • −Needs idempotent handlers and ordering care
  • −Event schema evolution must be managed

Cost Profile

Infrastructure

Low in-process; broker needed for cross-service use

Operational

Medium — monitor delivery and handler failures

Cognitive

Medium — indirect flow of control

Failure Modes

  • Event published but transaction rolled back, announcing a false fact

  • Lost events when the process crashes between commit and dispatch

  • Duplicate delivery causing repeated side effects

Real-World Examples

Alternatives

    Related Patterns

      Competency Domains

      distribution communicationdata state