Files
clean-architecture-frontend…/docs/architecture/capability-consumer-experience.md
T

157 lines
5.7 KiB
Markdown

# Capability consumer experience baseline
Correctness inside a reusable capability is not sufficient. The platform is
also evaluated by how much of that correctness a normal feature developer must
understand.
These measurements are baselines, not score targets. A lower line count is not
automatically better if it hides business semantics or creates a universal
repository abstraction.
## Scenario A — reference REST feature
The existing reference feature is the executable REST consumer.
It covers list/detail/create behavior, mapping, typed operation inputs,
application ports and an optimistic mutation path.
Current source size:
| Feature-owned area | LOC |
| --- | ---: |
| `application/reference-feature-api.ts` | 91 |
| `contracts/reference-mapper.ts` | 109 |
| `adapters/reference-http-gateway.ts` | 65 |
| `adapters/create-reference-feature-input.ts` | 41 |
| Total measured boundary/application source | 306 |
The important boundary metric is not the raw total. It is what those files need
to know about the platform.
Current result:
- HTTP platform imports in the feature adapter layer: 2 files,
- canonical import path used by both:
`src/adapters/http/index.ts`,
- direct imports of `http-execution-v3.ts`, retry scheduler, response reader,
auth admission or effect-certainty internals: 0,
- feature gateway owns transport failure projection: 0,
- feature gateway chooses operation ID, route ID, exact input/value type and
mapper: yes,
- central installed files own feature-specific adapter/runtime wiring: no;
feature-owned contributions are aggregated centrally.
The reusable HTTP capability now owns execution-outcome normalization through
`createFeatureHttpBinding`. A feature may still implement a custom outbound
adapter behind its application port when the reusable contract does not match
its business requirement.
## Scenario B — IndexedDB local draft
The original executable consumer probe remains at:
`tests/contract/consumer-experience/indexeddb-local-draft.test.ts`
with its isolated fixture:
`tests/contract/consumer-experience/fixtures/local-draft-feature.ts`
The architecture is now also exercised by a real vertical slice under:
`src/features/local-draft-feature`
It owns the Local Draft domain/application API and binds an
`IndexedDbRepositoryPort<LocalDraft, never>` in its feature adapter. The
cross-capability composition contract lives at:
`tests/contract/reusable-capability/feature-adapter-composition.test.ts`
That test composes one HTTP-only contribution and the IndexedDB-only Local Draft
contribution through the same generic catalog path.
The browser-level composition proof lives at:
`tests/browser-capabilities/local-draft-composition.spec.ts`
It runs against native browser IndexedDB. The test creates the platform
`createIndexedDbRuntime`, exposes that runtime through the typed
`createIndexedDbRepositoryProvider`, composes
`LOCAL_DRAFT_FEATURE_ADAPTER_CONTRIBUTION`, then executes Local Draft
save/find/remove through the feature API. The Local Draft feature still does
not import the IndexedDB runtime or native browser API.
Measured probe result:
| Metric | Result |
| --- | ---: |
| feature-owned fixture LOC | 97 |
| platform import statements | 1 |
| native IndexedDB API references | 0 |
| `src/adapters/storage/indexeddb/**` imports | 0 |
| runtime-internal IndexedDB types imported by feature | 0 |
The feature depends only on the public application boundary:
`src/application/ports/browser-file-storage/index.ts`
and specifically `IndexedDbRepositoryPort<LocalDraft, never>`.
The contract test rejects feature source that reaches for
`globalThis.indexedDB`, `IDBFactory`, `IDBDatabase`, `IDBTransaction`,
`IDBObjectStore` or the concrete IndexedDB adapter directory.
## What this does and does not prove
The contract probe confirms that **feature business code does not need native
IndexedDB knowledge** once an `IndexedDbRepositoryPort` has been composed.
The browser-capability proof additionally confirms that the same feature
contribution works when that port is backed by the repository's real
`createIndexedDbRuntime` and native IndexedDB implementation.
It does not prove that composition of `createIndexedDbRuntime` is cheap.
That constructor still owns substantial infrastructure policy:
- dataset scope and storage policy,
- physical store governance,
- retention/idempotency stores,
- schema migrations,
- codec and query policy,
- lifecycle authority,
- durability, scheduling and observation.
That complexity belongs at the composition/platform boundary, not in the
feature. The Local Draft vertical slice is now the second concrete consumer, and
it confirms that the stable seam is the typed repository provider plus a
feature-owned repository identity. It does **not** show that dataset scope,
retention, migration, codec or lifecycle-authority configuration can be safely
collapsed into one universal `create...Repository<T>` factory.
A convenience profile should therefore be introduced only after another
IndexedDB-backed product feature repeats the same infrastructure policy, not
merely because two features consume the same repository port.
## Consumer-quality review checklist
For each new product feature, record:
- feature-owned adapter LOC,
- platform glue LOC,
- files changed,
- central catalog edits,
- direct imports from capability-internal modules,
- native browser/network API references,
- duplicated failure/retry/lifecycle policy.
A healthy feature path should look like:
1. domain type and invariant,
2. use case,
3. business port,
4. feature-owned mapper/codec and policy,
5. capability-specific binding,
6. presentation controller/page.
The feature should not need the retry scheduler, abort ownership,
effect-certainty machinery, IndexedDB transaction lifecycle, OPFS journal,
reconnect coordinator or provider process model.