Here’s a fresh, high-impact system design topic that avoids overlap with the existing list:
How to design systems that treat state as a *derivative* of immutable events—not the source of truth
The Event Sourcing Pattern: Rebuilding State from Scratch in Distributed Systems Event sourcing inverts the traditional data model: instead of persisting the current state of an entity, you store an append-only log of every state-changing event.
🎯 System Scenario & Requirements
System Goal
The Event Sourcing Pattern: Rebuilding State from Scratch in Distributed Systems Event sourcing inverts the traditional data model: instead of persisting the current state of an entity, you store an append-only log of every state-changing event.
Actors & Components
- Application or API tier
- Core service under design
- Operator or SRE observer
Functional Requirements
- The Event Sourcing Pattern: Rebuilding State from Scratch in Distributed Systems Event sourcing inverts the traditional data model: instead of persisting the current state of an entity, you store an append-only log of e…
- The state itself is rebuilt on demand by replaying those events.
- This shift unlocks temporal queries, audit trails, and deterministic replays without sacrificing performance or consistency.
Constraints & Assumptions
⚡ Execution Stages & Decision Trace
Stage 1 — Core mental model
Apply jvm-internals-and-garbage-collection to Core mental model.
The system executes the mechanisms described for “Core mental model”.
Deep Dive
Scenario context
The Event Sourcing Pattern: Rebuilding State from Scratch in Distributed Systems
Event sourcing inverts the traditional data model: instead of persisting the current state of an entity, you store an append-only log of every state-changing event. The state itself is rebuilt on demand by replaying those events. This shift unlocks temporal queries, audit trails, and deterministic replays without sacrificing perfor…
This walkthrough focuses on How to design systems that treat state as a derivative of immutable events—not the source of truth.
Before making a design choice, clarify:
- Event sourcing inverts the traditional data model: instead of persisting the current state of an entity, you store an append-only log of every state-changing event.
- The state itself is rebuilt on demand by replaying those events.
- This shift unlocks temporal queries, audit trails, and deterministic replays without sacrificing performance or consistency.
Concepts in play: jvm-internals-and-garbage-collection, contract-testing, event-driven-architecture, api-gateway
Stage 1 — Core mental model
Observable outcome: You can explain how “Core mental model” changes the architecture or implementation path.
Stage 2 — Event stream anatomy
- An event is a past-tense fact: OrderPlaced , PaymentAuthorized , ShipmentDispatched .
- Each event carries a timestamp, a unique identifier, and the minimal payload needed to describe what happened.
- Events are never updated or deleted; corrections are published as new compensating events.
Observable outcome: Events are never updated or deleted; corrections are published as new compensating events.
Stage 3 — State reconstruction pipeline
- Load all events for an aggregate root (e.g., Order-123 ) from the event store.
- Instantiate an empty aggregate instance.
- Apply each event in chronological order by invoking the corresponding apply method on the aggregate.
- Return the fully hydrated state.
Observable outcome: Return the fully hydrated state.
Stage 4 — Java domain model
- public interface Event { UUID eventId(); Instant occurredOn(); } public record OrderPlaced(UUID eventId, Instant occurredOn, UUID orderId, List<OrderLine lines) implements Event {} public record OrderLine(UUID productId…
public interface Event {
UUID eventId();
Instant occurredOn();
}
public record OrderPlaced(UUID eventId, Instant occurredOn,
UUID orderId, List<OrderLine> lines) implements Event {}
public record OrderLine(UUID productId, int quantity, BigDecimal unitPrice) {}
public abstract class AggregateRoot {
private final List<Event> uncommittedEvents = new ArrayList<>();
protected void raise(Event event) {
uncommittedEvents.add(event);
apply(event);
}
protected abstract void apply(Event event);
public List<Event> uncommittedEvents() {
return List.copyOf(uncommittedEvents);
}
public void clearUncommittedEvents() {
uncommittedEvents.clear();
}
}
public class Order extends AggregateRoot {
private UUID id;
private OrderStatus status;
private List<OrderLine> lines = new ArrayList<>();
private BigDecimal total = BigDecimal.ZERO;
public Order(UUID id) {
this.id = id;
}
public void place(List<OrderLine> lines) {
if (status != null) throw new IllegalStateException("Order already exists");
raise(new OrderPlaced(UUID.randomUUID(), Instant.now(), id, lines));
}
@Override
protected void apply(Event event) {
if (event instanceof OrderPlaced placed) {
status = OrderStatus.PLACED;
lines.addAll(placed.lines());
total = placed.lines().stream()
.map(line -> line.unitPrice().multiply(BigDecimal.valueOf(line.quantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
}
public enum OrderStatus {
PLACED, PAID, SHIPPED, CANCELLED
}
Observable outcome: public interface Event { UUID eventId(); Instant occurredOn(); } public record OrderPlaced(UUID eventId, Instant occurredOn, UUID orderId, List<OrderLine lines) implements Event {} public record OrderLine(UUID productId…
Stage 5 — Event store contract
- A simple in-memory implementation:
public interface EventStore {
void append(UUID aggregateId, List<Event> events);
List<Event> load(UUID aggregateId);
List<Event> load(UUID aggregateId, Instant since);
}
@Service
public class InMemoryEventStore implements EventStore {
private final Map<UUID, List<Event>> store = new ConcurrentHashMap<>();
@Override
public void append(UUID aggregateId, List<Event> events) {
store.compute(aggregateId, (id, existing) -> {
var list = existing != null ? new ArrayList<>(existing) : new ArrayList<>();
list.addAll(events);
return list;
});
}
@Override
public List<Event> load(UUID aggregateId) {
return List.copyOf(store.getOrDefault(aggregateId, List.of()));
}
@Override
public List<Event> load(UUID aggregateId, Instant since) {
return load(aggregateId).stream()
.filter(e -> !e.occurredOn().isBefore(since))
.toList();
}
}
Observable outcome: A simple in-memory implementation:
Stage 6 — Spring REST endpoint
- @RestController @RequestMapping("/orders") public class OrderController { private final EventStore eventStore; public OrderController(EventStore eventStore) { this.eventStore = eventStore; } @PostMapping public Response…
@RestController
@RequestMapping("/orders")
public class OrderController {
private final EventStore eventStore;
public OrderController(EventStore eventStore) {
this.eventStore = eventStore;
}
@PostMapping
public ResponseEntity<UUID> placeOrder(@RequestBody PlaceOrderRequest request) {
var order = new Order(UUID.randomUUID());
order.place(request.lines());
eventStore.append(order.id(), order.uncommittedEvents());
order.clearUncommittedEvents();
return ResponseEntity.ok(order.id());
}
@GetMapping("/{id}")
public ResponseEntity<OrderView> getOrder(@PathVariable UUID id) {
var events = eventStore.load(id);
var order = new Order(id);
events.forEach(order::apply);
var view = new OrderView(order.id(), order.status(), order.lines(), order.total());
return ResponseEntity.ok(view);
}
}
public record PlaceOrderRequest(List<OrderLine> lines) {}
public record OrderView(UUID id, OrderStatus status, List<OrderLine> lines, BigDecimal total) {}
Observable outcome: @RestController @RequestMapping("/orders") public class OrderController { private final EventStore eventStore; public OrderController(EventStore eventStore) { this.eventStore = eventStore; } @PostMapping public Response…
End-to-end outcome
Event sourcing inverts the traditional data model: instead of persisting the current state of an entity, you store an append-only log of every state-changing event.
Alternatives considered
The source focuses on one primary design. A second architecture is not compared, so no alternative is invented here.
Failure and degraded modes
Trade-offs
- Detection: Trade-offs
- User or operator impact: Trade-offs
