Events¶
An event (notification) represents something that has already happened. Unlike requests, events can have zero or more handlers — this is the fan-out pattern.
Defining Events¶
IEvent is a protocol for event types. Implement it as a frozen dataclass:
For domain-driven designs where you control event identity and metadata, extend IEvent
with your own base class:
Event Handlers¶
EventHandler[TEvent] is an ABC with a handle method that returns None:
Registration¶
Bind an event type to a list of handler types:
Handlers across modules
Multiple modules can bind handlers for the same event type. waku's MessageRegistryAggregator
merges all registrations at application startup:
Both handlers will fire when OrderPlaced is published.
Publishing¶
Inject IPublisher and call publish. Prefer IPublisher over IMessageBus when you only need
to broadcast events — this enforces the principle of least privilege:
If no handlers are registered for an event type, publish is a no-op — it does not raise.
Domain events from aggregates
In domain-driven architectures, aggregates collect events internally. An infrastructure service bridges them to the message bus:
Event Dispatch¶
When bus.publish() is called, handlers execute sequentially in registration order.
If a handler raises, subsequent handlers do not run.
Ordering with routed handlers
The sequential guarantee applies to inline execution. When some handlers are routed to endpoints, inline and routed handlers run independently — there is no ordering guarantee across the boundary. Inline handlers execute immediately in the caller's scope, while routed handlers are processed asynchronously by endpoint workers.
Further reading¶
- Requests — commands, queries, and request handlers
- Pipeline Behaviors — cross-cutting middleware for request handling
- Message Bus — setup, interfaces, and complete example
- Event Sourcing — event-sourced aggregates, deciders, and projections