⚡ Event-Driven Architecture
CQRS & Event Sourcing
CQRS separates reads from writes — Event Sourcing stores state as a sequence of events
CQRS recognizes that reads and writes have fundamentally different requirements. Writes need consistency (ACID), reads need speed and queryability. Separating them lets you optimize each independently — and event sourcing gives you a complete audit trail as a side effect.
Key Concepts
Command Side (Write)
Commands mutate state. Write to a normalized, ACID-consistent database (PostgreSQL). Optimized for consistency and integrity, not query performance.
Query Side (Read)
Queries return data. Read from a denormalized, query-optimized store (Elasticsearch, Redis, DynamoDB). Shaped exactly for the UI/API consumer, not for normalization.
Event Sourcing
Instead of storing current state ('order status = SHIPPED'), store the sequence of events that produced it ('OrderCreated, PaymentProcessed, ShipmentDispatched'). Current state = replay all events.
Projection
A Projection subscribes to events and materializes them into a read model. The 'order dashboard' read model is built by consuming OrderCreated, StatusUpdated events and writing to Elasticsearch.
System Architecture Blueprint
CQRS Workflow
- **1. Write Phase:** User places an order. The Command Handler validates it and writes it to the Command DB (Event Store) — this is an append-only log.
- **2. Event Publishing:** The Event Store publishes the `OrderCreated` event to a Message Broker like Kafka.
- **3. Projection Phase:** A separate microservice (Projection) consumes the `OrderCreated` event and updates a highly optimized Read Model (like Elasticsearch for fast text search, or Redis for fast key-value lookup).
- **4. Read Phase:** The user's UI queries the Read API, which fetches the pre-computed, denormalized data directly from Elasticsearch. It doesn't need to do any expensive SQL JOINs.
- **Disaster Recovery:** If the Elasticsearch Read DB is ever corrupted, you can delete it completely. By replaying all events from Kafka from the beginning of time, you can rebuild the exact state of the Read DB.
Event Sourcing Example
event_store.jsonjson
1[
2 { "eventId": 1, "type": "OrderCreated", "aggregateId": "order-101", "data": { "userId": 5, "total": 100 } },
3 { "eventId": 2, "type": "PaymentProcessed", "aggregateId": "order-101", "data": { "method": "CreditCard" } },
4 { "eventId": 3, "type": "ItemShipped", "aggregateId": "order-101", "data": { "trackingId": "1Z999999" } }
5]
6
7// To get the current order state, the system replays these 3 events in order.
8// လက်ရှိအော်ဒါအခြေအနေကို သိချင်ရင် ဒီ Event ၃ ခုကို အစဉ်လိုက် ပြန် Run ကြည့်ပါတယ်။
9// This is exactly how a Bank Ledger works (deposits and withdrawals, rather than updating a single balance field).
10// ဒါက ဘဏ်စာအုပ် (Bank Ledger) တွေ အလုပ်လုပ်တဲ့ ပုံစံပါပဲ (လက်ကျန်ငွေကို သွားမပြင်ဘဲ၊ အသွင်း/အထုတ် တွေကိုပဲ အစဉ်လိုက် မှတ်သွားတာပါ)။💡
Senior Architect Insight: Event Sourcing is not free — you gain a perfect audit log and temporal query capability ('what was the state of this order 3 days ago?'), but you pay in complexity: eventual consistency, event schema versioning, and projection rebuilding. Use it only for domains where audit trails are legally required (like finance) or where temporal queries are highly valuable.