Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
112 lines
5.6 KiB
Markdown
112 lines
5.6 KiB
Markdown
# Clean Architecture layer contract
|
|
|
|
The import direction is `domain <- application <- presentation`; concrete
|
|
adapters implement application-owned ports and are assembled only in
|
|
`src/bootstrap`.
|
|
|
|
| Layer | Owns | May depend on |
|
|
| --- | --- | --- |
|
|
| `domain` | framework-neutral models and pure policies | domain siblings |
|
|
| `application` | use cases, ports, orchestration, view-models | domain and application siblings |
|
|
| `presentation` | routes, components, user interaction and view state | application public API and shared UI |
|
|
| `adapters` | browser and third-party implementations of application ports | application ports and limited domain values |
|
|
| `features/<id>` | removable vertical domain/application/contracts/adapters/presentation slice | the same inward rule plus platform public boundaries |
|
|
| `bootstrap` | runtime configuration, adapter construction and React mount | all selected runtime modules |
|
|
|
|
The following edges are forbidden:
|
|
|
|
- domain to application, presentation, adapters, bootstrap, React, or browser globals
|
|
- application to presentation, concrete adapters, bootstrap, React, or browser globals
|
|
- `contracts` to application or features: contracts is the lower package and
|
|
owns the shared vocabulary both of them read
|
|
- presentation to concrete adapters, raw DTO schemas, or storage implementations
|
|
- generic presentation to the installed-feature registries: which features exist
|
|
is a product decision owned by `bootstrap`
|
|
- an adapter to presentation, bootstrap internals, or another concrete adapter
|
|
- feature domain/application to its presentation or outbound adapter, and
|
|
feature presentation to its outbound adapter
|
|
|
|
## The adapter kernel
|
|
|
|
"Another concrete adapter" excludes the adapter kernel, which is shared on
|
|
purpose and is the only adapter code an adapter may reach across a group for:
|
|
|
|
- `src/adapters/platform/**` — the system clock, the shared abort primitive and
|
|
the bounded-capacity guard
|
|
- `src/adapters/browser-file-storage/result.ts` — the browser-data result and
|
|
failure constructors
|
|
|
|
Each rule above is enforced by `check:architecture`, including the kernel
|
|
carve-out, so this table and the executable rules cannot drift apart. Two edges
|
|
are still open and are named explicitly in `.dependency-cruiser.json` rather
|
|
than left silent: the generic presentation modules that read the installed
|
|
registries today, and the two collaborator types `query-cache` reads from
|
|
`cross-context-invalidation`. Both lists are frozen — a new edge of either kind
|
|
fails the gate.
|
|
|
|
## 어댑터 그룹의 공개 경계
|
|
|
|
각 어댑터 그룹의 공개 표면은 그 그룹의 `index.ts`다. 그룹 바깥
|
|
(`bootstrap`, `features`, `presentation`)은 배럴만 import한다.
|
|
`adapter-groups-are-reached-through-their-barrel` 규칙이 이를 강제하고,
|
|
`tests/fixtures/architecture/dependency-graph/barrel`이 거부와 허용을
|
|
각각 고정한다 — 규칙을 지우면 그 회귀 검사가 먼저 깨진다.
|
|
|
|
두 가지 예외가 있고 둘 다 의도된 것이다.
|
|
|
|
- **그룹 내부 파일끼리**는 파일 경로로 직접 import한다. 배럴은 바깥을 위한
|
|
문이지 내부 규율이 아니다.
|
|
- **어댑터 → 커널(`platform/**`)** 간선도 파일 경로를 유지한다. 커널은
|
|
런타임 합성물이 아니라 프리미티브이고, `check:adapter-inventory`가 네
|
|
소비자에게 `platform/abortable-operation.ts`로 해석되는 specifier를
|
|
직접 요구한다. 커널 배럴(`platform/index.ts`)은 bootstrap과 테스트를
|
|
위한 것이다.
|
|
|
|
`storage`는 최상위 배럴이 `indexeddb/`·`opfs/` 서브배럴을 재수출하지
|
|
않는다. 두 런타임이 각자 독립적으로 제거 가능하고
|
|
(`test:browser-file-storage-removal`), 그래서 각 서브배럴이 곧 경계다.
|
|
규칙의 도착점 정규식이 1단계 중첩 `index.ts`를 배럴로 인정하는 이유가
|
|
이것이다.
|
|
|
|
`service-worker/index.ts`는 `tsconfig.service-worker.json`의 `exclude`에
|
|
들어 있다. 그 설정이 그룹 폴더를 통째로 WebWorker lib로 컴파일하면서 페이지
|
|
realm 파일만 빼는 구조라, 배럴이 그 파일을 다시 끌어들이면 워커 타입체크가
|
|
`document`를 찾지 못한다. 배럴의 타입 커버리지는 `tsconfig.app.json`이
|
|
담당하고, 워커 진입점은 배럴을 쓰지 않는다.
|
|
|
|
`bootstrap` contains composition only. Business rules and page-specific
|
|
orchestration belong to domain/application.
|
|
|
|
The [ports, adapters, and feature-boundary contract](./frontend-ports-adapters-and-boundaries.md)
|
|
explains how `presentation` acts as the inbound adapter and how feature
|
|
application APIs augment the generic typed input registry. Concrete output
|
|
ports are composed in bootstrap and stay hidden behind the application facade.
|
|
Project-selected capabilities still implement the same output boundaries.
|
|
|
|
`check:architecture` keeps dependency-cruiser's report and adds the
|
|
authoritative TypeScript-aware graph below it:
|
|
|
|
```json
|
|
{
|
|
"staticImportGraph": {
|
|
"analyzer": "babel-parser-node-resolver",
|
|
"modules": [],
|
|
"dependencies": [],
|
|
"unresolved": [],
|
|
"parseFailures": [],
|
|
"cycles": [],
|
|
"violations": [],
|
|
"summary": { "errors": 0 },
|
|
"fixtureChecks": { "passed": true, "checks": [], "failures": [] }
|
|
}
|
|
}
|
|
```
|
|
|
|
The graph scans TypeScript and TSX, including static, dynamic, type, CommonJS
|
|
and JSDoc import references. It applies the path rules from
|
|
`.dependency-cruiser.json`, requires explicit TypeScript extensions for local
|
|
source imports, and fails closed on JavaScript-family source/specifiers,
|
|
unsupported rule shapes, unresolved imports, parse failures, error-severity
|
|
layer violations, or cycles. Regression fixtures prove the allowed resolver
|
|
path and each rejection class.
|