Adopt dependency-cruiser as the single declarative source for bounded-context + atomic-layer boundaries, replacing the per-context no-restricted-imports blocks that had to be hand-copied (and had left herregistratie uncovered). `.dependency-cruiser.js` encodes context direction (everyone→shared, herregistratie→registratie, showcase→*), domain-purity, contracts-import-nothing, ui↛infrastructure, ApiClient confinement, and no-circular. `npm run dep:check` enforces (wired into ci-local.sh + the frontend CI job); `npm run dep:graph` emits a committed mermaid context×layer graph. ESLint slimmed to no-explicit-any + template a11y. Docs + new-context skill updated to the single source. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
2.3 KiB
Markdown
50 lines
2.3 KiB
Markdown
---
|
|
name: new-context
|
|
description: Scaffold a new DDD bounded context (folders, path alias, boundary rules, lazy route). Use when adding a new business capability that doesn't belong in an existing context.
|
|
---
|
|
|
|
# New bounded context
|
|
|
|
A context is a business **capability**, not a user group — a user group is an actor
|
|
that may span contexts (ADR-0002). Check first whether the capability belongs in an
|
|
existing context; new contexts are rare.
|
|
|
|
Naming: domain contexts are **Dutch** (`registratie`, `herregistratie`); only
|
|
shared/reusable code is English. The context name is the ubiquitous language term.
|
|
|
|
## Steps
|
|
|
|
1. **Folders** — `src/app/<ctx>/{domain,application,infrastructure,ui}` (`contracts/`
|
|
only once it gets a wire seam). Empty layers can wait; don't scaffold placeholders.
|
|
2. **Path alias** — add `"@<ctx>/*": ["src/app/<ctx>/*"]` to `tsconfig.json` `paths`.
|
|
Aliases are direction statements; always import cross-context via the alias.
|
|
3. **Boundaries** (`.dependency-cruiser.js`, WP-38 — the single declarative source;
|
|
dependencies point inward and toward `shared` only). Add ONE `contextRule(...)` entry
|
|
for the new context listing the contexts it may **not** import (copy the `brief` leaf
|
|
example), and add the new context to the forbidden list of any context that must not
|
|
depend on it. The layer rules (`domain/` framework-free, `contracts/` import-nothing,
|
|
ApiClient confinement, `ui ↛ infrastructure`) match by glob and cover it automatically.
|
|
Verify with `npm run dep:check`; regenerate the graph with `npm run dep:graph`. (Boundaries
|
|
are no longer in `eslint.config.mjs` — that now holds only `no-explicit-any` + template a11y.)
|
|
4. **Route** — lazy child under the persistent shell in `app.routes.ts`:
|
|
|
|
```ts
|
|
{ path: '<ctx>', canActivate: [authGuard],
|
|
loadComponent: () => import('@<ctx>/ui/<ctx>.page').then(m => m.CtxPage) }
|
|
```
|
|
|
|
5. Build the first feature slice with the **new-feature** skill.
|
|
|
|
## Worked example
|
|
|
|
`src/app/brief/` — an independent leaf context (depends only on shared): see its
|
|
folder layout and its `contextRule` entry in `.dependency-cruiser.js`.
|
|
|
|
## Verify
|
|
|
|
```bash
|
|
npm run dep:check && npm run lint && npm run build
|
|
# prove the fence works: add a forbidden import (e.g. new ctx → @herregistratie/*),
|
|
# confirm `npm run dep:check` fails, remove it.
|
|
```
|