A backend project that demonstrates CQRS and event-driven architecture using Python and FastAPI.
This project separates write operations from read operations and connects both sides through an in-memory Event Bus / Message Broker.
- Create order
- Update order status
- Cancel order
- Publish domain events after write operations
- Project events into a separate read model
- Get order by ID from the read side
- List orders by customer from the read side
- Inspect published broker events
- Python
- FastAPI
- Pydantic
- Uvicorn
- CQRS Pattern
- Event Bus
- Message Broker
- Projection Handler
- Repository Pattern
flowchart TD
Client[Client / Swagger UI]
API[FastAPI Routes]
Client --> API
API --> WriteSide[Write Side]
API --> ReadSide[Read Side]
WriteSide --> Commands[Commands]
Commands --> CommandHandlers[Command Handlers]
CommandHandlers --> WriteRepo[OrderWriteRepository]
WriteRepo --> WriteStore[Write Model]
CommandHandlers --> Events[Domain Events]
Events --> EventBus[Event Bus]
EventBus --> Broker[In-Memory Message Broker]
Broker --> ProjectionHandler[OrderProjectionHandler]
ProjectionHandler --> ReadRepo[OrderReadRepository]
ReadRepo --> ReadStore[Read Model]
ReadSide --> Queries[Queries]
Queries --> QueryHandlers[Query Handlers]
QueryHandlers --> ReadRepo
sequenceDiagram
participant Client
participant API as FastAPI Route
participant CommandHandler
participant WriteRepo as Write Repository
participant EventBus
participant Broker as Message Broker
participant Projection as Projection Handler
participant ReadRepo as Read Repository
Client->>API: POST /orders
API->>CommandHandler: CreateOrderCommand
CommandHandler->>WriteRepo: save order
WriteRepo-->>CommandHandler: created order
CommandHandler->>EventBus: publish OrderCreatedEvent
EventBus->>Broker: send event
Broker->>Projection: deliver event
Projection->>ReadRepo: update read model
API-->>Client: created order response
| Event | Published When |
|---|---|
OrderCreatedEvent |
A new order is created |
OrderStatusUpdatedEvent |
An order status is changed |
OrderCancelledEvent |
An order is cancelled |
CQRS_pattern/
|-- commands/
| |-- cancel_order_command.py
| |-- cancel_order_handler.py
| |-- create_order_command.py
| |-- create_order_handler.py
| |-- update_order_status_command.py
| `-- update_order_status_handler.py
|-- events/
| `-- order_events.py
|-- infrastructure/
| |-- event_bus.py
| `-- message_broker.py
|-- models/
| `-- order.py
|-- projections/
| `-- order_projection_handler.py
|-- queries/
| |-- get_order_handler.py
| |-- get_order_query.py
| |-- list_orders_by_customer_handler.py
| `-- list_orders_by_customer_query.py
|-- repositories/
| |-- order_read_repository.py
| `-- order_write_repository.py
|-- main.py
|-- requirements.txt
`-- README.md
| Method | Endpoint | Purpose | Side |
|---|---|---|---|
POST |
/orders |
Create a new order | Command |
PATCH |
/orders/{order_id}/status |
Update order status | Command |
POST |
/orders/{order_id}/cancel |
Cancel an order | Command |
GET |
/orders/{order_id} |
Get order by ID | Query |
GET |
/customers/{customer_id}/orders |
List orders by customer | Query |
GET |
/broker/events |
Inspect published events | Eventing |
Install dependencies:
uv pip install -r requirements.txtStart the API:
uv run uvicorn main:app --reloadOpen Swagger UI:
http://127.0.0.1:8000/docs
curl -X POST http://127.0.0.1:8000/orders \
-H "Content-Type: application/json" \
-d '{"customer_id": 101, "items": ["Laptop", "Mouse"]}'This writes to the write repository and publishes OrderCreatedEvent.
curl -X PATCH http://127.0.0.1:8000/orders/1/status \
-H "Content-Type: application/json" \
-d '{"status": "CONFIRMED"}'This updates the write model and publishes OrderStatusUpdatedEvent.
curl -X POST http://127.0.0.1:8000/orders/1/cancelThis updates the write model and publishes OrderCancelledEvent.
curl http://127.0.0.1:8000/orders/1This reads from the projected read model.
curl http://127.0.0.1:8000/customers/101/ordersThis reads from the projected read model.
curl http://127.0.0.1:8000/broker/eventsExample response:
[
{
"topic": "order-events",
"event_type": "OrderCreatedEvent",
"payload": {
"order_id": 1,
"customer_id": 101,
"items": ["Laptop", "Mouse"],
"status": "CREATED"
}
}
]The event bus allows command handlers to publish business events without directly knowing who will consume them. The message broker stores and delivers those events to subscribers.
In this project, the OrderProjectionHandler subscribes to order events and updates the read model. This shows how a real system could use Kafka, RabbitMQ, or Redis Streams to keep write and read models synchronized.