Files
atomic-design-poc/backend/README.md
Edwin van den Houdt d08f3877f7 Architect-review remediation: enforce conventions, prod-safe tooling, one form idiom, resilience seams
Acts on the showcase review. Four workstreams; all tests green
(npm run lint, 70 FE tests, ng build, 33 backend tests).

Enforcement + CI:
- eslint.config.mjs bans `any` and enforces layer/context boundaries
  (domain ≠ Angular; herregistratie → registratie → shared, auth → shared);
  `npm run lint` added; ajv 6 scoped to ESLint via nested override.
- .github/workflows/ci.yml: FE lint+check:tokens+test+build, backend dotnet test,
  and an API-client drift check.

One form idiom (the headline finding):
- change-request-form converged onto the wizard pattern — change-request.machine.ts
  (Model/Msg/reduce + value objects) + submit-change-request.ts (Result) + a real
  POST /api/v1/change-requests (server re-validates). Spec + story added; the detail
  page no longer holds an ad-hoc success signal.

Resilience/observability seam:
- api-client.provider.ts: request timeout, X-Correlation-Id, Idempotency-Key for
  writes; comments naming the retry/auth seams.
- Backend logs correlation id + a no-PII submit-audit line; /api/v1 prefix +
  backward-compat note; client regenerated.

Quick wins:
- Dev tooling excluded from prod: scenario.interceptor wired only under isDevMode()
  (?scenario= inert in prod); debug panel @if(isDev) (tree-shaken out).
- src/environments + apiBaseUrl into provideApiClient (angular.json fileReplacements).
- Backend /health + /health/ready.
- Debug view PII-minimised (redactProfile: name/address/DOB redacted, BIG masked).
- IntakePolicyAdapter (removes inline resource in the intake wizard).
- README de-staled; CLAUDE.md gains EN/NL + forms-one-idiom + lint/CI notes.
- Stories: text-input, link, data-row, site-header, site-footer, change-request-form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 08:25:51 +02:00

113 lines
4.1 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 database, no real BRP/DUO: data is in-memory and seeded (`Data/SeedData.cs`),
but the endpoints, DTOs, status codes and error envelope are production-shaped.
## 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.