Skip to main content
Articles/System Design/here-s-a-fresh-high-impact-system-design-topic-that-avoids-overlap-with-the-existing-list
Scenario Article

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

Abstract Algorithms•Jul 21, 2026
💡 Executive Summary

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

1

Stage 1 — Core mental model

Action / Decision:

Apply jvm-internals-and-garbage-collection to Core mental model.

Component Behavior:

The system executes the mechanisms described for “Core mental model”.

Observable Output: Callers and operators can verify the stage outcome for “Core mental model”.
Read Full Stage in Deep Dive ↓
📖

Deep Dive

Detailed Architectural Analysis

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

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit