Event-Driven Architecture in System Designing
advance · System Designing
In standard microservice design, services communicate synchronously via REST APIs. While clean, this introduces direct temporal coupling: if Service A calls Service B, both systems must be online and functioning at that exact millisecond for the transaction to succeed. Event-Driven Architecture (EDA) completely flips this paradigm. Instead of requesting actions, components react to Events —a state change or significant occurrence that has already happened in the past (e.g., OrderPlaced , PaymentProcessed ). 1. Loose Coupling Between Services In an EDA environment, components are broken down into Event Producers and Event Consumers , completely mediated by a centralized event broker (like Apache Kafka or RabbitMQ). The Producer doesn't care who is listening. When a user buys an item, the OrderService simply publishes an OrderPlaced event to the broker and goes back to work. It has no awareness of how many systems need that data. The Consumers subscribe to the broker independently. The NotificationService reads the event to send a confirmation email, the InventoryService reads it to deduct stock, and the AnalyticsService reads it to track sales trends. If the NotificationService crashes, the rest of the ecosystem continues running flawlessly. When it boots back up, it simply reads its place in the event log and catches up. 2. Event Sourcing: Events as the Source of Truth In a traditional database model, you only store the current state of the world. If a user changes their shipping address, you overwrite the old address in the row. The history of how they got to that state is lost forever. Event Sourcing shifts the source of truth from the current state to an append-only log of immutable historical events . Instead of saving status: "delivered" , the database stores a sequence of individual life events: OrderCreated (Timestamp: 10:00) PaymentReceived (Timestamp: 10:01) AddressUpdated (Timestamp: 10:05) OrderShipped (Timestamp: 12:00) Replaying State: To find the current state of any order at any moment, the application fetches the raw event ledger from an Event Store and "replays" the events sequentially from scratch. The Audit Holy Grail: This provides an absolute, un-falsifiable audit log. It is heavily utilized in banking ledger tracking, medical record histories, and complex supply chain logistics. 3. CQRS (Command Query Responsibility Segregation) Event Sourcing introduces a major performance bottleneck: if you have millions of historical events, replaying them from scratch every time a user wants to view their profile dashboard causes massive read latency. To solve this, architectures deploy CQRS . This pattern strictly separates the data models used to write data from the data models used to read data. The Split Workflow: The Command Side (Writes): Handles operations that mutate data ( POST , PUT , DELETE ). It accepts input, validates business logic, and writes the raw sequence of events directly into an Event Store optimized for fast appending. The Projection Engine: A background process listens to the incoming stream of events from the Event Store, executes the math, and constructs a highly denormalized, flat snapshot of the data. The Query Side (Reads): Handles operations that fetch data ( GET ). It queries the pre-computed read database (which can be a fast document store like MongoDB or an in-memory cache like Redis). It never touches the write log. 4. Structural Comparison: Standard vs. CQRS + Event Sourcing Architectural Layer Traditional CRUD Model Event Sourcing + CQRS Data Storage Single relational table (mutable state) Append-only event store (immutable history) Read Operations Complex JOIN queries against live tables Zero-join reads against pre-computed projection views Scaling Difficult to scale due to lock contention on single rows Highly scalable; writes and reads scale completely independently Implementation Cost Low complexity; fast initial delivery High complexity; requires handling eventual consistency and schema evolution 5. The Primary Trade-off: Eventual Consistency Because the read database in a CQRS setup is updated via background events after the write database commits, the system is eventually consistent . If a user hits Submit Order , the write database registers the event instantly. However, if the network processing the projection engine lags by 200 milliseconds, and the user is immediately redirected to a dashboard that queries the read database, they might see a "No active orders" screen. Designing an event-driven system requires incorporating asynchronous UX patterns (such as optimistic UI updates, web sockets, or polling mechanisms) to manage this data delay gracefully.