Interview contents
Event Sourcing Pattern
Introduction
Traditional systems persist current state in a database:
- Orders table has the latest order record.
- Bank accounts store the latest balance.
But this approach loses valuable history. For example:
- How did the account balance change over time?
- Which events led to the current state?
The Event Sourcing Pattern solves this by storing all changes (events) to application state as an append-only log.
- Current state is reconstructed by replaying events.
- Provides complete audit trails, replayability, and integration opportunities.
Intent
The Event Sourcing Pattern’s intent is to persist application state as a sequence of events, not just the latest snapshot, enabling auditability, traceability, and replay.
Structure
Core Components
-
Event Store
- Append-only log of domain events.
-
Event Publisher
- Persists and publishes new events.
-
Event Handlers / Projectors
- Consume events to update read models.
-
Read Models (Projections)
- Query-optimized views derived from events.
graph TD A[Command] --> B[Event] B --> C[Event Store] C --> D[Read Model Projector] D --> E["(Read Model)"]✅ Source of truth = events.
✅ Read models are projections.
Participants
-
Commands
- User intent that triggers domain behavior.
-
Domain Events
- Immutable records of what happened.
- Example:
OrderPlaced,PaymentCompleted.
-
Event Store
- Stores events in order, append-only.
-
Projections
- Build query-optimized models.
Collaboration Flow
- Command executes business logic.
- Emits domain event.
- Event persisted in Event Store.
- Handlers project event into read models.
Implementation in Java
Domain Event
public class OrderPlacedEvent { private final String orderId; private final double total; private final Instant timestamp;
public OrderPlacedEvent(String orderId, double total) { this.orderId = orderId; this.total = total; this.timestamp = Instant.now(); } public String getOrderId() { return orderId; } public double getTotal() { return total; } public Instant getTimestamp() { return timestamp; }}Event Store
public interface EventStore { void save(Object event); List<Object> getEvents(String aggregateId);}Event-Sourced Repository
public class OrderRepository implements EventStore { private final Map<String, List<Object>> store = new HashMap<>();
@Override public void save(Object event) { store.computeIfAbsent("orders", k -> new ArrayList<>()).add(event); }
@Override public List<Object> getEvents(String aggregateId) { return store.getOrDefault(aggregateId, new ArrayList<>()); }}Projection (Read Model)
@Servicepublic class OrderProjection { private final Map<String, Double> orderTotals = new HashMap<>();
public void on(OrderPlacedEvent event) { orderTotals.put(event.getOrderId(), event.getTotal()); }
public Double getTotal(String orderId) { return orderTotals.get(orderId); }}✅ Events stored, not just final state.
✅ Read model derived from replay.
Consequences
Benefits
- Auditability – Complete history of changes.
- Replayability – Rebuild state anytime.
- Integration – Events published to other systems.
- Debugging – Understand exact sequence of actions.
- Event-Driven Friendly – Naturally integrates with CQRS.
Drawbacks
- Complexity – More moving parts.
- Storage Growth – Event store grows indefinitely.
- Event Versioning – Schema evolution is tricky.
- Consistency – Must rebuild projections carefully.
Real-World Case Studies
1. Banking Systems
- Transactions stored as events.
- Balance derived by summing transactions.
2. E-commerce Platforms
- Orders tracked as events (
OrderPlaced,OrderShipped). - Enables customer history and analytics.
3. Event Stores (Tools)
- Axon Framework, EventStoreDB, Kafka.
- Provide ready-made support for event sourcing.
Extended Java Case Study
Traditional Approach (CRUD)
// Latest state onlypublic class BankAccount { private String id; private double balance;
public void deposit(double amount) { balance += amount; } public void withdraw(double amount) { balance -= amount; }}❌ Loses history of deposits/withdrawals.
Event Sourcing Approach
public class BankAccount { private String id; private double balance; private List<Object> changes = new ArrayList<>();
public void deposit(double amount) { applyChange(new MoneyDepositedEvent(id, amount)); }
public void withdraw(double amount) { applyChange(new MoneyWithdrawnEvent(id, amount)); }
private void applyChange(Object event) { if(event instanceof MoneyDepositedEvent e) balance += e.getAmount(); if(event instanceof MoneyWithdrawnEvent e) balance -= e.getAmount(); changes.add(event); }}✅ Full history retained.
✅ Balance reconstructed from events.
Interview Prep
Q1: What is the Event Sourcing Pattern?
Answer: A pattern where application state is persisted as a sequence of events, not just final state, enabling auditability and replay.
Q2: What are pros and cons of event sourcing?
Answer: Pros: auditability, replayability, integration. Cons: complexity, storage growth, event versioning.
Q3: How does event sourcing relate to CQRS?
Answer: Commands produce events (write side). Queries read projections built from events (read side).
Q4: When should you use event sourcing?
Answer: In systems needing auditability, history, or replay (banking, trading, e-commerce).
Q5: What challenges exist?
Answer: Event schema evolution, storage growth, rebuilding projections.
Visualizing Event Sourcing Pattern
graph TD Cmd[Command] --> EV[Event] EV --> Store[Event Store] Store --> Proj[Projection] Proj --> RM["(Read Model)"]✅ Events as source of truth.
✅ Read models derived from projections.
Key Takeaways
- Event Sourcing stores events, not just final state.
- Benefits: auditability, replayability, integration.
- Challenges: complexity, storage, event versioning.
- Works well with CQRS and event-driven systems.
Next Lesson
Next, we’ll cover Saga Pattern — managing long-running distributed transactions in microservices.