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

5.7 KiB

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.