feat(event-subscriber): NRC event subscriber + rebuildable read projection (closes #7) #59
Reference in New Issue
Block a user
Delete Branch "feat/7-event-subscriber-projection"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What & why
S-06 wires the upstream event path's destination. A new event-subscriber consumes NRC
zaken/zaak/createnotifications (the abonnement callback) and projects them into a rebuildable read model; a new projection-api serves it. Builds directly on the OZ→NRC notification wiring from #56 (ADR-0007).Per a design decision, the projection is notification-only: it stores
id+status=INGEDIEND.bsn/naam_placeholderare deferred — the NRC notification doesn't carry the bsn and the subscriber may not read OpenZaak directly (§8.1). Idempotency and "rebuild" both run off a durableprocessed_notificationslog, so a rebuild needs no OpenZaak access. See ADR-0008.Closes #7
Definition of Done
test(event-subscriber)→ greenfeat(event-subscriber)).refs #7).docker compose upreaches green health — verify-stack brought the full stack up and its health gate passed (~5.5 min job; bring-up well within budget).docs/architecture/adr-0008-read-projection-store.md).docs/demo-script.md.Notes for reviewers
Architecture decision to eyeball (ADR-0008): the projection DB is shared by the writer (event-subscriber) and reader (projection-api) as one CQRS read-model bounded context. This nudges §8.5 ("each service owns its schema"); I judged it the textbook read-model split (one derived, rebuildable store) rather than a cross-domain DB reach, and documented the reasoning.
New dependency: EF Core 10 +
Npgsql.EntityFrameworkCore.PostgreSQL(first EF use in the repo).dotnet-efpinned as a local tool;NuGetAuditMode=directkeeps EF's design-time-only tooling transitive (NU1903) out of the shipped, audited graph.Verification done locally: isolated live end-to-end against real containers + Postgres + EF migrate — 401 without the callback bearer (NRC's probe contract), 204 with it, an INGEDIEND projection row, idempotent replay, and rebuild-from-log. Unit + acceptance + 100% mutation all green.
Not verifiable locally (env, not code): the full OZ→NRC→subscriber path needs egress to
selectielijst.openzaak.nl(to publish a zaaktype → create a zaak) and a stabledocker compose; the local box is podman with neither. The newverify-projectionstep in theverify-stackjob exercises it in CI. The NRC→subscriber hop reuses the driver/contract already proven by #56.Follow-ups: populate
bsn/naam_placeholdervia an ACL zaak-read (§8.1-clean); provision a persistent abonnement (needs a dotted network alias — single-label hosts aren't URL-valid for NRC).Add the projection persistence and the two services around it: - Projection.ReadModel: a shared EF Core (Npgsql) read model owning the projection schema — register_projection + the subscriber's processed_notifications log — plus EfProjectionStore / EfNotificationLog (atomic record-or-skip on the PK for idempotency) and the initial migration. One rebuildable store, written by the subscriber and read by projection-api (ADR-0008). - EventSubscriber.Api: POST /notifications NRC callback (enforces the abonnement bearer, 401 without it per ADR-0007), POST /admin/rebuild, /health. Migrates on start. - ProjectionApi.Api: GET /register, GET /register/{id}, /health — the read side. dotnet-ef pinned as a local tool for migrations; NuGetAuditMode=direct so EF's design-time-only tooling transitive doesn't flag the shipped build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>