Gap analysis found the POC's designed-but-unbuilt strategic gaps: ABAC authorization (ADR-0002/PRD-0002 phase P1), no e2e coverage, unproven i18n second-locale seam, thin resilience seams (correlation-id, idempotency, retry), and in-memory-only persistence. Each WP is grounded in the current code (file paths + line numbers), not just the analysis. Also corrects PRD-0001's stale 'Proposed' status header — the Mijn aanvragen vertical is fully built. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.1 KiB
WP-22 — Durable persistence (optional tier)
Status: todo Phase: 5 — productie-volwassenheid
Why
Every backend store (ApplicationStore, DocumentStore, BriefStore) is a
static Dictionary guarded by a single lock object, explicitly documented as
in-memory ("no DB", per backend/README.md and CLAUDE.md's own framing). Data —
including the audit log — is lost on every restart. This is a deliberate POC
simplification (CLAUDE.md lists "runtime DTO validation on every endpoint" and
similar as out-of-scope, and a database was never promised), but it's the one gap
that would visibly break the moment someone tries to run this as a real demo across
multiple sessions or deploys it anywhere that restarts (e.g. most PaaS platforms
recycle instances).
This WP is marked optional tier — lower priority than WP-18/19/20/21 — because unlike auth/e2e/i18n/resilience, the current in-memory design is explicitly documented and defensible for a POC. Do this when the POC needs to survive restarts (demoing over multiple days, deploying somewhere with instance recycling), not speculatively.
Read first
backend/README.md(the "in-memory seeded, no DB" framing to preserve or supersede)backend/src/BigRegister.Api/Data/ApplicationStore.cs,backend/src/BigRegister.Api/Data/DocumentStore.cs,backend/src/BigRegister.Api/Data/BriefStore.cs— the three stores, eachstatic Dictionary+lockbackend/src/BigRegister.Api/Data/SeedData.cs(current in-memory seed — becomes a first-run DB seed)docs/architecture/0001-bff-lite-decision-dtos.md(confirm this WP doesn't touch the decision-DTO contracts — persistence is purely behind the existing store interfaces)
Decisions (pre-made, don't relitigate)
- SQLite + EF Core, not a heavier database — matches the POC's zero-external- infrastructure posture (no docker service to add, no connection string to manage beyond a file path) while proving real persistence.
- Persistence lives entirely behind the existing static-class store APIs — the
public methods on
ApplicationStore/DocumentStore/BriefStorekeep their signatures; only the implementation swaps fromDictionarytoDbContext. No endpoint or domain-rule code changes (Program.cs,Domain/*). - Seed on empty DB, not on every startup —
SeedDataruns once (checked via "is the DB empty") so restarts don't reset demo data, which is the entire point of this WP. - Document bytes stay a deliberate exception if storage size becomes a concern: either store them as a BLOB column (simplest, consistent with "one DB, no extra infra") or explicitly punt file bytes to disk with only metadata in SQLite — decide based on actual seeded file sizes, don't over-engineer a blob-storage abstraction for a POC.
- Audit log becomes a real table, not just "no longer volatile" — this closes
the "audit log is in-memory" gap named in the original gap analysis alongside
persistence, since it's the same static-dict problem in
DocumentStore.cs.
Files
backend/src/BigRegister.Api/BigRegister.Api.csproj— addMicrosoft.EntityFrameworkCore.Sqlite+Microsoft.EntityFrameworkCore.Design.- New
backend/src/BigRegister.Api/Data/AppDbContext.cs—DbSets mirroring the three stores' current in-memory shapes (StoredDocument,AuditEntry, whateverApplicationStore/BriefStorehold internally — read those files first to avoid redesigning the shape, just relocate it). backend/src/BigRegister.Api/Data/ApplicationStore.cs,DocumentStore.cs,BriefStore.cs— convert static dictionary methods toDbContext-backed queries; keep every public method signature identical (this is the acceptance bar — a signature change means a caller inProgram.csorDomain/*needs to change, which should be zero).backend/src/BigRegister.Api/Data/SeedData.cs— becomes "seed if empty" run once at startup against the real DB.backend/src/BigRegister.Api/Program.cs— registerAppDbContext(DI), run migrations/EnsureCreated+ conditional seed at startup.- New EF Core migration (generated via
dotnet ef migrations add Initial). .gitignore— exclude the runtime.dbfile (ship the migration, not the database).backend/README.md— update "in-memory seeded, no DB" framing to describe the SQLite file and its lifecycle (created/seeded on first run, persists thereafter, delete the file to reset demo data).docker-compose.yml— mount a volume for the SQLite file sodocker compose uprestarts don't lose data either (currently theapi-bin/api-objvolumes exist for build caching only, not data).
Steps
- Add the EF Core packages; define
AppDbContextmatching the current in-memory record shapes exactly (no schema redesign in this WP). - Convert one store at a time (
DocumentStorefirst — it's the smallest and has the audit log, which is the most valuable win), keepingbackend/tests/BigRegister.Tests/*green after each conversion. - Wire
AppDbContext+ startup migration/seed inProgram.cs. - Convert
ApplicationStore, thenBriefStore. - Update
docker-compose.ymlwith a persistent volume; updatebackend/README.md. - Full backend test suite + a manual restart test: run the backend, create an application, restart the process, confirm the application still exists.
Acceptance criteria
- All three stores are EF Core/SQLite-backed; no
static Dictionaryremains inData/*.csfor application/document/brief state. - Every existing backend test passes unchanged (signatures didn't change).
- Restarting the backend process preserves previously created applications, documents, and brief drafts (manually verified).
- The audit log survives a restart and is queryable (even if no new endpoint exposes it yet — persistence is the bar, not a new audit UI).
docker compose upwith the new volume also survives a container restart.
Verification
cd backend && dotnet test. Manual: dotnet run --project src/BigRegister.Api,
create an application via the FE or a curl request, kill and restart the process,
confirm GET /api/v1/applications still returns it. Repeat with docker compose up
docker compose restart api.
Out of scope
A production-grade database (Postgres/SQL Server) — SQLite is the deliberate,
right-sized choice for a POC that still wants to prove real persistence. Migrating
existing in-memory demo data on upgrade (a fresh SQLite file starts from
SeedData, same as today's in-memory start). Blob storage for document bytes
beyond a BLOB column (only revisit if seeded files are large enough to matter).
Risks
EF Core's async patterns don't drop in as a 1:1 replacement for synchronous
dictionary lookups — endpoint handlers in Program.cs currently call store methods
synchronously; converting to async/await may ripple further than "just the
Data/ layer" if minimal-API handlers aren't already async. Check this before
starting and budget for handler signature changes (still not a behavior change,
but a wider diff than the Files section implies if handlers need async added).