feat(domain): BIG Domain Service skeleton with the Registration aggregate (closes #6) #61

Merged
not merged 16 commits from feat/6-domain-service into main 2026-07-01 08:46:22 +00:00
Contributor

What & why

S-05: the BIG Domain Service skeleton with the Registration aggregate. Submitting a
registration creates the aggregate (INGEDIEND), starts the Flowable registratie process, and
— via a hosted external-task worker — opens a zaak through the ACL and records it on the aggregate.

This is the upstream half of the walking skeleton: it produces the zaak that S-06 (#7) projects.

Orchestration (ADR-0009): POST /registrations is fast and returns 202; the
OpenZaakAanmaken external-worker job is driven off the request path by a polling
BackgroundService. The Workflow Client is the only code that talks to Flowable (§8.2); the
worker opens the zaak only through the ACL (§8.1). The REST contract was verified against a live
flowable-rest engine.

Layering mirrors the other services: Big.Domain (pure) → Big.Application (use cases over
ports) → Big.Infrastructure (Flowable + ACL clients, in-memory store, job pump) → Big.Api.

Closes #6
Closes #60

Definition of Done

  • Linked Gitea issue (above).
  • Failing test committed before the implementation (red→green for every layer).
  • Implementation makes the test pass; refactor where structure improved.
  • Conventional Commits referencing the issue (refs #6).
  • CI green — all Gitea Actions jobs (first real run is on this PR; see note below).
  • docker compose up reaches green health checks (domain service health-checked, verified locally).
  • Docs updated — ADR-0009 + demo-script note.
  • ADR added — docs/architecture/adr-0009-external-task-job-worker.md.
  • Demo note in docs/demo-script.md.

Notes for reviewers

  • Mutation: Stryker baseline for the domain service is 97.7% (break threshold 90, the repo
    floor). The OpenZaakJobPump hosted-shell is excluded; two genuinely-equivalent mutants are
    // Stryker disable-d with reasons.
  • Scope decisions (ADR-0009): registration persistence is in-memory for this slice (the
    read path is the projection, S-06) — an EF-backed domain store is a documented follow-up; the
    IRegistrationStore port keeps that additive. The state machine is minimal (INGEDIEND only).
  • verify-domain end-to-end: seeds a published BIG zaaktype, recreates the acl service
    pointed at it (the zaaktype URL is server-assigned, so it isn't knowable at bring-up) host-
    consistently, then submits a registration and asserts the worker opens a zaak. This leg
    (domain→ACL→OpenZaak) could not be exercised in the dev environment (no selectielijst egress /
    full compose); it mirrors the configuration the ACL integration test already proves, so CI is its
    first real run.
  • Adds an overridable ACL_OPENZAAK_BASEURL compose var (default unchanged) and a machine-readable
    ZAAKTYPE_URL line from seed_catalogus.py.

Closes #62

Update — CI fix folded in

ac874bf makes the mutation job's Stryker report uploads best-effort (continue-on-error: true): the Gitea artifact backend returns 500 to upload-artifact@v3 (server-side), which was failing the job on every run (main included) despite the ratchet passing. The mutation gate stays the Stryker ratchet. See gitea-actions-gotchas.md §4 (#62).

🤖 Generated with Claude Code

## What & why S-05: the **BIG Domain Service** skeleton with the `Registration` aggregate. Submitting a registration creates the aggregate (`INGEDIEND`), starts the Flowable `registratie` process, and — via a hosted external-task worker — opens a zaak through the ACL and records it on the aggregate. This is the upstream half of the walking skeleton: it produces the zaak that S-06 (#7) projects. **Orchestration (ADR-0009):** `POST /registrations` is fast and returns 202; the `OpenZaakAanmaken` external-worker job is driven off the request path by a polling `BackgroundService`. The Workflow Client is the only code that talks to Flowable (§8.2); the worker opens the zaak only through the ACL (§8.1). The REST contract was verified against a live flowable-rest engine. Layering mirrors the other services: `Big.Domain` (pure) → `Big.Application` (use cases over ports) → `Big.Infrastructure` (Flowable + ACL clients, in-memory store, job pump) → `Big.Api`. Closes #6 Closes #60 ## Definition of Done - [x] Linked Gitea issue (above). - [x] Failing test committed before the implementation (red→green for every layer). - [x] Implementation makes the test pass; refactor where structure improved. - [x] Conventional Commits referencing the issue (`refs #6`). - [x] CI green — all Gitea Actions jobs (first real run is on this PR; see note below). - [x] `docker compose up` reaches green health checks (domain service health-checked, verified locally). - [x] Docs updated — ADR-0009 + demo-script note. - [x] ADR added — `docs/architecture/adr-0009-external-task-job-worker.md`. - [x] Demo note in `docs/demo-script.md`. ## Notes for reviewers - **Mutation:** Stryker baseline for the domain service is **97.7%** (break threshold 90, the repo floor). The `OpenZaakJobPump` hosted-shell is excluded; two genuinely-equivalent mutants are `// Stryker disable`-d with reasons. - **Scope decisions (ADR-0009):** registration persistence is **in-memory** for this slice (the read path is the projection, S-06) — an EF-backed domain store is a documented follow-up; the `IRegistrationStore` port keeps that additive. The state machine is minimal (`INGEDIEND` only). - **`verify-domain` end-to-end:** seeds a published BIG zaaktype, recreates the `acl` service pointed at it (the zaaktype URL is server-assigned, so it isn't knowable at bring-up) host- consistently, then submits a registration and asserts the worker opens a zaak. This leg (domain→ACL→OpenZaak) could not be exercised in the dev environment (no `selectielijst` egress / full compose); it mirrors the configuration the ACL integration test already proves, so CI is its first real run. - Adds an overridable `ACL_OPENZAAK_BASEURL` compose var (default unchanged) and a machine-readable `ZAAKTYPE_URL` line from `seed_catalogus.py`. Closes #62 ### Update — CI fix folded in `ac874bf` makes the `mutation` job's Stryker report uploads best-effort (`continue-on-error: true`): the Gitea artifact backend returns 500 to `upload-artifact@v3` (server-side), which was failing the job on **every** run (main included) despite the ratchet passing. The mutation gate stays the Stryker ratchet. See gitea-actions-gotchas.md §4 (#62). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
not added 12 commits 2026-06-30 15:58:48 +00:00
The Domain Service drives the OpenZaakAanmaken external-worker task as a
hosted job worker (PRD §36): POST /registrations starts the registratie
process and returns; a polling worker acquires the job, opens a zaak via
the ACL (§8.1), attaches the zaak URL to the aggregate, and completes the
job. The Workflow Client is the only Flowable client (§8.2); the worker
logic is an Application service over ports. Registration state is in-memory
for the minimal slice (the read path is the projection, S-06).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Failing unit tests for the Registration aggregate root: a submission starts
in INGEDIEND carrying its bsn, an empty/whitespace/null bsn is rejected, the
started process-instance id is remembered, and attaching the zaak the ACL
opened records its URL idempotently (a conflicting URL is rejected) while the
status stays INGEDIEND.

The aggregate is a stub (no-op mutators, empty bsn) so the tests compile and
fail on their assertions; the green commit implements the invariants.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Submit requires a bsn and starts the aggregate in INGEDIEND; the started
process-instance id is recorded; AttachZaak stores the ACL's zaak URL,
tolerating a duplicate (at-least-once worker delivery) and rejecting a
conflicting URL, without advancing the status.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Failing application-layer tests over fake ports (IWorkflowClient, IAclClient,
IRegistrationStore):
- Submit persists an INGEDIEND registration and starts the registratie process,
  recording the instance id — and persists *before* starting, so the worker can
  correlate the OpenZaakAanmaken job back to its aggregate (ADR-0009).
- The worker opens a zaak via the ACL and attaches it; an unknown registration
  throws (job left for redelivery); a redelivered job is idempotent and opens no
  second zaak (§8.6).

Handlers are stubs (no persistence / no ACL call) so the tests compile and fail
on their assertions; the green commit implements them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SubmitRegistration creates the aggregate, persists it, starts the registratie
process via the Workflow Client, records the instance id and upserts. OpenZaakWorker
loads the correlated registration, opens a zaak via the ACL, attaches it and saves;
an unknown registration throws (job redelivered), and an already-opened zaak short-
circuits without opening a second one (§8.6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Failing infrastructure unit tests (stub HttpMessageHandler, fakes):
- FlowableWorkflowClient starts a process with the registrationId variable and
  returns the instance id; acquires OpenZaakAanmaken jobs (topic/workerId/lock)
  and parses their registrationId; completes a job with the zaakUrl variable —
  request URIs match flowable-rest's service/ and external-job-api/ paths.
- AclHttpClient POSTs the bsn to the ACL and returns the zaak URL.
- InMemoryRegistrationStore saves/reads/upserts by id.
- OpenZaakJobProcessor acquires, opens a zaak, completes the job; leaves a failing
  job uncompleted for redelivery; polls harmlessly when idle.

Adapters are stubs so the tests compile and fail on their assertions; the green
commit implements them against the REST contract verified on a live Flowable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FlowableWorkflowClient speaks flowable-rest's REST API (Basic auth): start a
registratie process with the registrationId variable, acquire OpenZaakAanmaken
external-worker jobs and parse their registrationId, complete a job with the
zaakUrl variable — the contract verified against a live engine. AclHttpClient
POSTs the bsn to the ACL and returns the zaak URL. InMemoryRegistrationStore is
a concurrent-dictionary upsert. OpenZaakJobProcessor drains parked jobs, opening
a zaak per job and completing it, leaving failures for redelivery; OpenZaakJobPump
is the hosted polling shell that drives it on an interval (ADR-0009).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The BIG Domain Service Api wires the use cases and the hosted job worker:
POST /registrations creates the aggregate and starts the registratie process,
returning 202 with a location; GET /registrations/{id} reads the aggregate so
the eventually-opened zaak URL can be observed (ADR-0009). The Workflow Client
is registered once behind both Flowable ports; the ACL client and in-memory
store complete the wiring. Verified against a live flowable-rest: submit starts
a parked process and the worker polls it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use-case-level BDD (Reqnroll) for S-05: a zorgprofessional submits a registration;
the Domain Service starts the registratie process and the OpenZaakAanmaken external
task opens a zaak via the ACL, recorded on the aggregate (ADR-0009). Driven against
in-memory Workflow Client and ACL stand-ins; real Flowable+ACL+OpenZaak delivery is
the live-stack verify-domain check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stryker.NET config for the domain service (break 90, the repo's ratchet floor),
excluding the OpenZaakJobPump hosted-shell from mutation. Hardened the unit tests
to kill survivors — Basic-credential value, variable types, null/failure response
paths, option defaults, guard clauses, save counts and log output — leaving only
two documented equivalent mutants (Stryker-disabled). make mutation runs the domain
ratchet and CI uploads its report alongside the others.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dockerfile (multi-stage, .NET 10) + .dockerignore for the BIG Domain Service; a
'domain' service in infra/docker-compose.yml (health-checked, depends on acl healthy
and flowable-init completed). run-domain-check.sh drives the full path against the up
stack — seed a published zaaktype, recreate the acl pointed at it (host-consistent),
POST /registrations, and assert the worker opens a zaak and records it. Wired as the
verify-domain Makefile target + a verify-stack CI step; domain added to WAIT_SVCS and
the log dump. seed_catalogus.py now emits a machine-readable ZAAKTYPE_URL line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(domain): demo note for submitting a registration (S-05) (refs #6)
Some checks failed
CI / lint (pull_request) Successful in 1m4s
CI / build (pull_request) Successful in 48s
CI / unit (pull_request) Successful in 58s
CI / mutation (pull_request) Failing after 17m2s
CI / verify-stack (pull_request) Successful in 6m2s
67f0ffb88d
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not added the type:slicearea:domainarea:workflow labels 2026-06-30 15:58:59 +00:00
not added 1 commit 2026-07-01 07:32:24 +00:00
ci(mutation): make Stryker report upload best-effort (refs #62)
Some checks failed
CI / build (pull_request) Successful in 1m0s
CI / unit (pull_request) Successful in 55s
CI / mutation (pull_request) Successful in 3m16s
CI / lint (pull_request) Failing after 14m2s
CI / verify-stack (pull_request) Successful in 6m12s
ac874bf746
The Gitea artifact backend returns 500 to actions/upload-artifact@v3 (server-side,
distinct from the @v4 GHES guard). With if: always() that 500 failed the whole
mutation job even though the ratchet passed — red on main and on every PR. Mark the
three report uploads continue-on-error: true so the mutation *gate* stays the Stryker
ratchet (make mutation's exit code), not the report upload. Documented in
gitea-actions-gotchas.md §4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not added 1 commit 2026-07-01 08:09:06 +00:00
ci: retrigger CI after gitea restart (refs #6)
All checks were successful
CI / lint (pull_request) Successful in 1m36s
CI / build (pull_request) Successful in 50s
CI / unit (pull_request) Successful in 57s
CI / mutation (pull_request) Successful in 3m36s
CI / verify-stack (pull_request) Successful in 5m47s
72efab3ae0
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not merged commit 2f5d656b54 into main 2026-07-01 08:46:22 +00:00
Sign in to join this conversation.