Files
atomic-design-poc/backend/README.md
Edwin van den Houdt 556f2f47bf feat(fp): WP-22 — durable persistence (SQLite/EF Core)
Applications, documents (+ audit log) and the brief move off static in-memory
Dictionaries onto a real SQLite file via EF Core, so demo data survives a
process restart or `docker compose restart api` for the first time. The three
stores (ApplicationStore/DocumentStore/BriefStore) keep their exact public
signatures and static-class shape — no DI, no async ripple into Program.cs's
minimal-API handlers — each method just opens a short-lived AppDbContext via
Db.Create() under the same lock it already had. Opaque nested shapes (a
wizard's draft snapshot, a brief's sections/placeholders/status) are stored as
JSON text columns rather than redesigned into relational tables, matching the
existing "don't interpret it" posture.

Found two things the WP's own text got wrong, corrected in
docs/backlog/WP-22-durable-persistence.md's Deviations section: SeedData never
seeded these three stores (only the read-only BRP/DUO-mimicking GETs, which
stay in-memory) so there's no seed step; and no new docker-compose volume is
needed since the existing bind mount already covers the SQLite file — verified
against this environment's real podman-backed compose stack, not just by
reading the file.

Also: pinned SQLitePCLRaw.bundle_e_sqlite3 to 3.0.3 (EF Core Sqlite's own
transitive default bundles a pre-3.50.2 SQLite with a known high-severity
memory-corruption advisory); found and fixed a real xUnit test race where
concurrent test-class hosts stomped a shared static connection-string field,
fixed by disabling cross-class test parallelization rather than adding DI the
stores don't otherwise need.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 10:19:23 +02:00

123 lines
4.7 KiB
Markdown

# BIG-register BFF (ASP.NET Core)
The backend that hosts the **business rules** for the BIG-register portal. The
frontend renders the decisions this service computes; it does not recompute them
(BFF-lite + decision DTOs — see `../docs/architecture/0001-bff-lite-decision-dtos.md`).
No real BRP/DUO: the reference data they'd return (registration, person, diplomas,
notes — `Data/SeedData.cs`) is in-memory and seeded, but the endpoints, DTOs,
status codes and error envelope are production-shaped.
**Applications, documents and the brief persist** to a SQLite file
(`src/BigRegister.Api/bigregister.db`, EF Core-backed — `Data/AppDbContext.cs`,
`Data/Db.cs`) created and migrated on first run; restarting the process (or
`docker compose restart api` — the existing `./backend:/src` bind mount already
covers it, see `docker-compose.yml`) does **not** lose data. Delete the file to
reset demo data back to empty, the same state a fresh clone starts from. This is
a deliberate, right-sized choice for a POC (SQLite, no external DB service) — see
`docs/backlog/WP-22-durable-persistence.md`.
## Run
### Everything (docker-compose, from repo root)
```bash
docker compose up
```
- App: <http://localhost:4200>
- Swagger UI: <http://localhost:5000/swagger>
### Backend only (local)
```bash
cd backend
dotnet run --project src/BigRegister.Api
# → http://localhost:5000/swagger
```
### Frontend against a local backend
```bash
npm start # ng serve, proxies /api → http://localhost:5000 (proxy.conf.json)
```
### Tests
```bash
cd backend && dotnet test # rule unit tests + endpoint integration tests
```
## API
| Method | Route | Purpose |
|---|---|---|
| GET | `/api/dashboard-view` | registration + person + computed herregistratie decision |
| GET | `/api/notes` | specialisms / aantekeningen |
| GET | `/api/brp/address` | BRP address lookup (`gevonden:false` = no address) |
| GET | `/api/duo/diplomas` | diplomas with derived profession + applicable policy questions, + manual fallback |
| GET | `/api/intake/policy` | scholing threshold (config value) |
| POST | `/api/registrations` | submit registration → reference, or 422 (manual diploma) |
| POST | `/api/herregistraties` | submit re-registration → reference, or 422 (0 hours) |
| POST | `/api/intakes` | submit intake → reference, or 422 (0 hours) |
Rejections use **ProblemDetails (RFC 7807)** with status **422**. Every request
carries an `X-Correlation-Id` (set by the FE fetch adapter); the backend echoes it
into a no-PII submit-audit log line (`kind`, `outcome`, `reference`, correlation id)
— the seam for real structured logging / an audit store.
### Versioning
Endpoints live under **`/api/v1`**. Additive changes (a new optional field) stay on
v1: the NSwag-generated client and the FE `parse*` boundary ignore unknown fields,
so old clients keep working. A breaking change (renamed/removed field, changed
semantics) is introduced as **`/api/v2`** served alongside v1 until clients migrate.
## Where the rules live (`src/BigRegister.Api/Domain/`)
- `Diplomas/DiplomaRules.cs` — profession derivation + which policy questions apply.
- `Registrations/HerregistratieRule.cs` — eligibility + reason + status invariant.
- `Intake/IntakePolicy.cs` — scholing threshold.
- `Submissions/SubmissionRules.cs` — submit rejections + reference generation.
## Typed client (NSwag)
The frontend calls this API through a generated TypeScript client. Regenerate it
from the contract after a **shape** change:
```bash
npm run gen:api # builds backend → swagger.json → src/app/shared/infrastructure/api-client.ts
```
## Maintainability: changing a policy is one backend change
**Goal:** require every *Verpleegkundige* diploma to confirm a Dutch skills
assessment. This is a new policy question on a diploma type.
Edit **one file**`Domain/Diplomas/DiplomaRules.cs`:
```diff
public static IReadOnlyList<PolicyQuestion> QuestionsFor(Diploma d)
{
var questions = new List<PolicyQuestion>();
if (d.Engelstalig)
questions.Add(NlTaalEngelstalig);
+ if (d.Opleiding == "verpleegkunde")
+ questions.Add(new PolicyQuestion(
+ "bekwaamheid",
+ "Heeft u in de afgelopen vijf jaar een bekwaamheidstoets afgelegd?",
+ QuestionType.JaNee));
return questions;
}
```
Rebuild the backend (`docker compose up` or `dotnet run`). The new question now
appears in the registration wizard for HBO-Verpleegkunde.
- **No frontend change.** The FE renders whatever questions the API returns.
- **No client regeneration.** The wire shape (`PolicyQuestionDto`) is unchanged —
only the data behind it. `npm run gen:api` is only needed when a DTO *shape* changes.
Add a unit test for the new rule in `tests/BigRegister.Tests/RuleTests.cs` and
you're done.