Event-Driven Architecture
lexigram-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.
1. Commands vs. Events
Section titled “1. Commands vs. Events”Lexigram 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 lexigram.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( RegisterUser(user_id="u1", email="ada@example.com") )See the lexigram-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 lexigram.events import DomainEvent
class UserRegistered(DomainEvent): user_id: str email: strSubscribing with a decorator
Section titled “Subscribing with a decorator”from lexigram.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)await bus.publish([UserRegistered(user_id="u1", email="ada@example.com")])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
lexigram-eventspackage — sagas, projections, and store backends