epic-12: after epic audit and init commit
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
---
|
||||
name: "Adr 0009 Replay Engine Projection Separation"
|
||||
description: "Generic replay engine separated from domain projections"
|
||||
depth: 2
|
||||
links: ["../index.md", "../modules/core-events-submodule-spec.md", "../modules/core-sessions-submodule-spec.md"]
|
||||
---
|
||||
|
||||
# ADR 0009: Generic Replay Engine and Projection Separation
|
||||
|
||||
**status:** accepted
|
||||
**date:** 08.05.2026
|
||||
**deciders:** Kami
|
||||
|
||||
---
|
||||
|
||||
## context
|
||||
|
||||
Correx uses an append-only event store as the single source of truth.
|
||||
|
||||
Session state and future domain states are reconstructed entirely from ordered event streams. Replay determinism and projection purity are core system invariants.
|
||||
|
||||
During implementation of Epic 2 (Session Lifecycle FSM), a distinction emerged between:
|
||||
|
||||
* generic replay infrastructure
|
||||
* domain-specific projection semantics
|
||||
|
||||
The system already contained reusable replay infrastructure in `:core:events`:
|
||||
|
||||
* `Projection<S>`
|
||||
* `StateBuilder<S>`
|
||||
* `EventReplayer<S>`
|
||||
|
||||
At the same time, session lifecycle logic introduced:
|
||||
|
||||
* `SessionProjector`
|
||||
* `SessionFsm`
|
||||
* `SessionState`
|
||||
* `SessionEvent`
|
||||
|
||||
A design decision was required to prevent:
|
||||
|
||||
* replay logic duplication
|
||||
* infrastructure/domain coupling
|
||||
* projection semantics leaking into the event engine
|
||||
|
||||
---
|
||||
|
||||
## content
|
||||
|
||||
The replay architecture is separated into two layers:
|
||||
|
||||
### 1. Generic replay engine (`:core:events`)
|
||||
|
||||
The `:core:events` module owns:
|
||||
|
||||
* replay orchestration
|
||||
* projection execution contracts
|
||||
* state folding mechanics
|
||||
* ordered event replay infrastructure
|
||||
|
||||
This layer contains:
|
||||
|
||||
* `Projection<S>`
|
||||
* `StateBuilder<S>`
|
||||
* `EventReplayer<S>`
|
||||
* replay implementations
|
||||
|
||||
This layer MUST remain domain-agnostic.
|
||||
|
||||
It MUST NOT contain:
|
||||
|
||||
* session lifecycle logic
|
||||
* FSM semantics
|
||||
* domain state models
|
||||
* business transition rules
|
||||
|
||||
---
|
||||
|
||||
### 2. Domain projections (`:core:sessions`)
|
||||
|
||||
The `:core:sessions` module owns:
|
||||
|
||||
* session lifecycle semantics
|
||||
* FSM transition logic
|
||||
* session state reconstruction
|
||||
* event interpretation for session domain
|
||||
|
||||
This layer contains:
|
||||
|
||||
* `SessionState`
|
||||
* `SessionStatus`
|
||||
* `SessionEvent`
|
||||
* `SessionFsm`
|
||||
* `SessionProjector`
|
||||
|
||||
`SessionProjector` implements:
|
||||
|
||||
```kotlin
|
||||
Projection<SessionState>
|
||||
```
|
||||
|
||||
and is executed by the generic replay engine.
|
||||
|
||||
---
|
||||
|
||||
### 3. Replay flow
|
||||
|
||||
Replay is defined as:
|
||||
|
||||
```text
|
||||
EventStore
|
||||
↓
|
||||
EventReplayer<S>
|
||||
↓
|
||||
Projection<S>
|
||||
↓
|
||||
State
|
||||
```
|
||||
|
||||
For sessions:
|
||||
|
||||
```text
|
||||
EventStore
|
||||
↓
|
||||
EventReplayer<SessionState>
|
||||
↓
|
||||
SessionProjector
|
||||
↓
|
||||
SessionState
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Projection purity rules
|
||||
|
||||
All projections MUST be:
|
||||
|
||||
* deterministic
|
||||
* side-effect free
|
||||
* replay-safe
|
||||
* stateless outside reduction input
|
||||
|
||||
Projections MUST NOT:
|
||||
|
||||
* perform IO
|
||||
* access system clocks
|
||||
* mutate external state
|
||||
* depend on runtime execution order outside event sequence
|
||||
|
||||
Projection output MUST depend solely on:
|
||||
|
||||
* previous state
|
||||
* current event
|
||||
|
||||
---
|
||||
|
||||
### 5. Session replay semantics
|
||||
|
||||
Session state is not persisted directly.
|
||||
|
||||
`SessionState` is reconstructed entirely from replaying ordered `StoredEvent` streams.
|
||||
|
||||
FSM transitions are interpreted from persisted `EventPayload` instances through session-specific mapping logic.
|
||||
|
||||
---
|
||||
|
||||
## consequences
|
||||
|
||||
**positive:**
|
||||
|
||||
* replay engine becomes reusable across domains
|
||||
* deterministic replay guarantees remain centralized
|
||||
* domain logic remains isolated from persistence mechanics
|
||||
* multiple independent projections can coexist over same event stream
|
||||
* session lifecycle logic remains testable in isolation
|
||||
* future projections can reuse replay infrastructure without duplication
|
||||
|
||||
**negative:**
|
||||
|
||||
* introduces additional abstraction layers
|
||||
* domain projections require explicit wiring into replay engine
|
||||
* event interpretation requires mapper logic between payloads and FSM events
|
||||
* debugging replay chains may require traversing multiple layers
|
||||
|
||||
---
|
||||
|
||||
## status
|
||||
|
||||
accepted
|
||||
Reference in New Issue
Block a user