Event Sourcing
debt(d9/e9/b9/t5)
Closest to 'silent in production until users hit it' (d9). The detection_hints field explicitly states 'automated: no' and describes a business-requirements-level code pattern. There is no linter, SAST, or compiler check that can detect misapplication of event sourcing — the consequences (diverged state, missing audit history, performance degradation from no snapshots) only surface in production under real load or during an incident investigation.
Closest to 'architectural rework' (e9). The quick_fix describes 'rebuild current state by replaying events, never update in place' — this is not a one-line patch. Adopting or correcting event sourcing requires redesigning storage (append-only event store), rebuilding all state-derivation logic, implementing snapshot strategies, and potentially introducing CQRS projections. Removing it from an existing system is equally demanding. This is a full architectural rework.
Closest to 'defines the system's shape' (b9). The applies_to field spans web, cli, and queue-worker contexts, meaning the choice permeates the entire application. Every aggregate, every read model, every deployment concern, every operational practice (replay, snapshot, compaction) is shaped by this decision. The tags (architecture, cqrs, ddd) confirm it is a load-bearing architectural choice that defines the system's fundamental structure — you rewrite or you live with it.
Closest to 'notable trap' (t5). The misconception field explicitly documents the canonical trap: developers believe event sourcing and CQRS must always be used together, conflating two independent patterns. The common_mistakes also list storing current state alongside events (causing divergence) and modifying past events. These are well-documented gotchas that most developers encounter when first adopting the pattern, matching the 't5 — documented gotcha most devs eventually learn' anchor.
Also Known As
TL;DR
Explanation
Event Sourcing persists every state change as an immutable domain event (OrderPlaced, ItemAdded, PaymentProcessed) in an append-only event store. Current state is reconstructed by replaying the event stream. Benefits: complete audit trail, ability to reconstruct state at any point in time, temporal debugging, and natural fit with CQRS (events feed read model projections). Challenges: eventual consistency, snapshot strategies for long-lived aggregates, event schema evolution, and increased operational complexity. PHP implementations include EventSauce, Broadway, and Prooph.
Diagram
flowchart TD
CMD[Command] --> AGG[Aggregate]
AGG --> E1[OrderPlaced event]
AGG --> E2[PaymentReceived event]
AGG --> E3[OrderShipped event]
E1 & E2 & E3 --> STORE[(Event Store)]
STORE -->|Replay| STATE[Current State]
STORE -->|Project| READ[(Read Models)]
style STORE fill:#6e40c9,color:#fff
style STATE fill:#238636,color:#fff
style READ fill:#238636,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Storing current state alongside events — the two will eventually diverge; current state must be derived from events.
- Modifying or deleting past events — events are immutable; use compensating events for corrections.
- No snapshot strategy for aggregates with thousands of events — replaying from the beginning becomes too slow.
- Using event sourcing for simple CRUD — the complexity is only justified when audit history, replay, or temporal queries add real value.
Avoid When
- Simple CRUD applications — event sourcing adds enormous complexity for no benefit when there is no need for history.
- Teams unfamiliar with the pattern — incorrect event schema design is very hard to reverse once events are stored.
- High-frequency write workloads where replaying thousands of events to rebuild state is too slow.
- When eventual consistency is unacceptable and reads must always reflect the latest write instantly.
When To Use
- Audit-critical domains (finance, healthcare, legal) where every state change must be recorded and replayable.
- Systems that need time-travel debugging — replaying events to reproduce bugs at any point in history.
- Collaborative editing or conflict resolution where the sequence of changes matters.
- Decoupled read/write models (CQRS) where projections are built from the event stream.
Code Examples
// Mutable event store — breaks event sourcing guarantees:
class EventStore {
public function update(int $id, array $data): void {
$this->db->update('events', $data, ['id' => $id]); // Events must be immutable!
}
}
// Events are the source of truth — state is derived by replaying them
class BankAccount {
private int $balance = 0;
private array $events = [];
public function deposit(int $amount): void {
$this->apply(new MoneyDeposited($amount));
}
private function apply(DomainEvent $event): void {
$this->events[] = $event;
match($event::class) {
MoneyDeposited::class => $this->balance += $event->amount,
MoneyWithdrawn::class => $this->balance -= $event->amount,
};
}
public function getEvents(): array { return $this->events; }
public function getBalance(): int { return $this->balance; }
}