oridecon-events provides a full CQRS and event-sourcing stack: command, query, and event buses, an event store, projections, and sagas. It decouples your domain from transactional infrastructure.
uv add oridecon-eventsEvent classes land in domains/ (or modules/<slug>/domains/). Handlers are module-local; the provider registers them.
1. Commands vs. Events
Section titled “1. Commands vs. Events”Oridecon separates two kinds of message:
- Commands — an intent to change state (
RegisterUser). Routed to exactly one handler via the Command Bus. - Events — a fact that already happened (
UserRegistered). Broadcast to many subscribers via the Event Bus.
graph LR
User[User / System] -- dispatch --> CB[Command Bus]
CB -- handle --> Agg[Aggregate / Service]
Agg -- publish --> EB[Event Bus]
EB -- notify --> Sub[Subscribers / Projections]
2. Commands & the Command Bus
Section titled “2. Commands & the Command Bus”A command is a simple message; the CommandBus routes it to its single handler. Inject the bus contract and dispatch:
from oridecon.events import Command, CommandBus
class RegisterUser(Command): user_id: str email: str
class RegistrationController: def __init__(self, commands: CommandBus) -> None: self._commands = commands
async def register(self) -> None: await self._commands.dispatch( )See the oridecon-events package docs for command-handler registration.
3. Events & the Event Bus
Section titled “3. Events & the Event Bus”Events notify the rest of the system of a state change. Define a DomainEvent:
from oridecon.events import DomainEvent
class UserRegistered(DomainEvent): user_id: str email: strSubscribing with a decorator
Section titled “Subscribing with a decorator”from oridecon.events import event_handler
@event_handler(UserRegistered)async def welcome_new_user(event: UserRegistered) -> None: # send email, create profile, … ...Subscribing and publishing directly
Section titled “Subscribing and publishing directly”# Register a handlerbus.subscribe(UserRegistered, welcome_new_user)
# Publish events (e.g. an aggregate's pending events)The event bus dispatches handlers in parallel by default, with per-handler retries and a dead-letter queue — all tunable via the events.event_bus config.
4. Event Sourcing & Projections
Section titled “4. Event Sourcing & Projections”For domains where history matters, persist events instead of just current state:
- Event store — an append-only log of every domain event. Select the backend with
events.event_store_backend(memory,postgres, ormongodb). - Projections — read models built by subscribing to the event stream and updating a specialized table for queries or search.
events: enabled: true event_store_backend: postgres # memory | postgres | mongodb event_bus: parallel_dispatch: true enable_dead_letter: true command_bus: max_retries: 3 timeout_seconds: 30.05. When to Use Which Bus
Section titled “5. When to Use Which Bus”Prefer the Command Bus for inter-module communication within an application — modules stay decoupled and interact only through well-defined command contracts. Use Events to notify many independent subscribers of something that already happened.
Next Steps
Section titled “Next Steps”- Background Jobs — offloading event side effects to workers
- Multi-Tenancy — tenant-scoped event streams
- Project Structure — where
gen event/gen commandwrite files oridecon-eventspackage — sagas, projections, and store backends