Skip to content

ADR-0028: Persistence Pattern Boundaries — Three Legitimate Strategies

  • Status: Accepted
  • Date: 2026-07-23
  • Deciders: Architecture grilling session (issue #480)

Context

Issue #480 identified that three persistence patterns coexist in workers/src/: EntityRepository, EventStore, and direct D1. The initial concern was that this overlap was accidental and should be unified.

After research of industry literature (Martin Fowler, Greg Young, Cloudflare D1 docs, AWS Well-Architected Framework, PowerSync, RxDB), we concluded that all three patterns serve distinct legitimate purposes. The friction was not the existence of three patterns, but the lack of documented boundaries for choosing one over another.

Decision

Adopt the following persistence strategy boundaries, documented as the canonical decision tree for every new data access in the worker:

Decision Tree

Is the data concern an infrastructure/system table?
  ├── YES → Use Direct D1 (getDB().prepare())
  │         Examples: auth queries, idempotency ledger, storage proxy

  └── NO → Is the operation a READ or a WRITE?
      ├── READ → Use EntityRepository<T> via createReadHandler
      │           Examples: list students, get class, find user

      └── WRITE → Use createCommandHandler → applyEvent → events table
                    Examples: create/update/delete student, class, role, user

Boundary Details

PatternWhat it's forFilesTest Adapter
Direct D1Infrastructure queries (auth, idempotency, system tables)workers/src/db/queries.ts, route handlers for authmockD1.ts (SQL-string-matching)
EntityRepository<T>Domain reads (list, findById, paginated queries with scope)workers/src/storage/repository.ts, d1EntityRepo.ts, memoryEntityRepo.tsMemoryEntityRepo (in-memory Map)
EventStore + applyEventDomain writes (create, update, delete) with atomic audit trailworkers/src/events/eventStore.ts, applyEvent.ts, createCommandHandler.tsMock EventStore implementations

Rules

  1. Infrastructure queries never go through EntityRepository or EventStore. Auth lookup, idempotency check, and system-table reads use direct D1. Performance-critical, no domain logic.

  2. Domain reads never go through EventStore. The events table stores audit history, not current state. Reads always query projection tables via EntityRepository<T>.

  3. Domain writes always go through EventStore. Every create, update, or delete writes:

    • A row to the events table (audit trail)
    • A row to the relevant projection table Both in the same D1EventStore.executeBatch() atomic operation. This is the command handler pattern from ADR-0023.
  4. No entity mixes patterns in the same service file. A service file receives either EntityRepository<T> (for reads) or uses applyEvent (for writes), never both. If it needs both, split into two modules or accept the repository + a read-handler.

  5. Legacy entity-specific event tables (student_events, attendance_events, user_events) are DEPRECATED. All new writes go to the unified events table. The legacy tables remain in the schema for backward compatibility with existing data but are not written to by new code. A future migration will backfill and drop them.

Consequences

Positive

  • Clear decision framework. A developer adding a new endpoint can answer "Direct D1? Repository? EventStore?" with a single yes/no decision tree.
  • Each pattern has a test strategy. Direct D1 → mockD1 (SQL strings), EntityRepository → MemoryEntityRepo, EventStore → mock EventStore.
  • No more accidental mixing. The boundary lines are explicit and documented.

Negative

  • Three patterns to learn. New developers must understand all three, but now they know when to use each instead of guessing.
  • Migration burden. Legacy event tables need eventual cleanup. Low urgency.

Neutrals

  • This ADR ratifies existing practice rather than prescribing new architecture. Most of the codebase already follows these boundaries — the ADR just names them.
  • ADR-0023 remains the convergence plan. This ADR complements it by documenting why three patterns exist, not how to unify them.

References

Distribuído sob licença MIT.