composition-root

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloudflare Composition Root

Cloudflare 组合根

Use a composition root to build application dependencies from runtime capabilities at their owning lifecycle. Apply system entrypoint identification and dependency bootstrap before placing construction or invocation code. Inner code receives ports, never
Env
, raw bindings, framework service locators, or binding names.
text
Cloudflare entrypoint
  → tracer + parsed configuration
  → binding adapters
  → exact service dependencies
  → application service
  → protocol projection
Apply
../typescript-standards/SKILL.md
for TypeScript contracts, parsing, errors, side effects, and tests. When implementation shape is unclear, consult EXAMPLES.md for TanStack Start, WorkerEntrypoint, adapter, and refactor templates.
使用组合根,依据运行时能力所属的生命周期构建应用依赖。 在放置构建或调用代码之前,先应用系统入口点识别与依赖引导。 内部代码只接收端口,绝不接收
Env
、原始绑定、框架服务定位器或绑定名称。
text
Cloudflare entrypoint
  → tracer + parsed configuration
  → binding adapters
  → exact service dependencies
  → application service
  → protocol projection
有关 TypeScript 契约、解析、错误处理、副作用和测试,请应用
../typescript-standards/SKILL.md
。如果实现结构不明确,请查阅 EXAMPLES.md,了解 TanStack Start、WorkerEntrypoint、适配器和重构模板。

Ownership

职责归属

  • Composition root: builds adapters and services for their owning scope and supplies them to operational entrypoints.
  • Entrypoint adapter: parses external input, establishes invocation context, invokes a service with supplied dependencies, and projects its result.
  • Binding adapter: implements an application-owned port using KV, R2, D1, Durable Objects, Queues, service bindings, Workflows, or another external capability.
  • Application service: owns one use case's sequencing, fallback, retry, and best-effort policy.
  • Domain module: owns pure values and invariants.
  • Port: names exactly the capability a consumer needs; it does not mirror a Cloudflare API.
Raw platform values may exist only in composition roots and binding adapters. A binding adapter accepts the smallest platform capability, not all of
Env
.
  • 组合根: 为其所属作用域构建适配器和服务,并将其提供给各个运行入口点。
  • 入口点适配器: 解析外部输入、建立调用上下文、使用提供的依赖调用服务,并投影其结果。
  • 绑定适配器: 使用 KV、R2、D1、Durable Objects、Queues、service bindings、Workflows 或其他外部能力,实现由应用拥有的端口。
  • 应用服务: 负责单个用例的调用顺序、回退、重试和尽力而为策略。
  • 领域模块: 负责纯值和不变量。
  • 端口: 准确命名消费者所需的能力,不映射 Cloudflare API。
原始平台值只能存在于组合根和绑定适配器中。绑定适配器只接受所需的最小平台能力,而不是完整的
Env
。

1. Map the boundary

1. 绘制边界

Inspect repository instructions and the affected:
  • TanStack Start
    src/server.ts
    , request context type, middleware order, and server functions;
  • Worker, WorkerEntrypoint, Durable Object, Workflow, queue, and scheduled entrypoints;
  • Env
    declarations and every use of the relevant binding;
  • tracer creation, correlation, error reporting, and detached-work ownership;
  • existing services, adapters, ports, tests, and architecture checks.
Trace each affected behavior from entrypoint to side effect and caller-visible result. For refactors, record current error, retry, fallback, serialization, and tracing behavior before moving it.
Completion criterion: Every affected entrypoint, binding consumer, behavior, lifecycle, side effect, failure policy, and existing test seam is accounted for.
检查仓库说明以及以下受影响的实现:
  • TanStack Start 的
    src/server.ts
    、请求上下文类型、中间件顺序和 server functions;
  • Worker、WorkerEntrypoint、Durable Object、Workflow、队列和定时入口点;
  • Env
    声明以及相关 binding 的每处使用;
  • tracer 创建、关联、错误报告和分离任务的所有权;
  • 现有服务、适配器、端口、测试和架构检查。
沿入口点到副作用和调用方可见结果,追踪每项受影响的行为。对于重构,在移动代码之前记录当前的错误、重试、回退、序列化和追踪行为。
完成标准: 每个受影响的入口点、binding 消费者、行为、生命周期、副作用、失败策略和现有测试接缝都已被纳入考虑。

2. Design application-owned dependencies

2. 设计由应用拥有的依赖

Define contracts from consumer needs:
ts
/** Persistence capability required by document application services. */
export interface DocumentStore {
  /** Find one document by its domain ID. */
  find(id: DocumentId): Promise<Document | null>;
  /** Persist one valid document. */
  save(document: Document): Promise<void>;
}

/** Exact dependencies of the publish-document use case. */
export type PublishDocumentDependencies = Readonly<{
  documents: DocumentStore;
  clock: Clock;
  tracer: Tracer;
}>;
Rules:
  • Use domain inputs and parsed outputs, not raw keys, strings, request objects, or binding options.
  • Expose only operations required by real callers.
  • Keep each service's dependency object exact; never create a shared mega-bag or rename
    Env
    to
    Dependencies
    .
  • Put platform serialization, keyspaces, pagination tokens, TTLs, metadata, and output parsing in the binding adapter.
  • Let the application service own policy. The adapter translates failures precisely enough for the service to distinguish recoverable conditions from corruption and defects.
  • Inject
    Tracer
    into services and adapters that create spans; do not add it to every method signature.
  • Add an application-owned factory only when scope is genuinely dynamic after composition, such as account-bound construction after authentication. The factory must not expose
    Env
    or raw bindings.
Choose lifetimes explicitly:
  • Reuse immutable adapters and services when their bindings and tracer are safe across concurrent invocations.
  • Construct per invocation when they retain request-scoped runtime objects or mutable invocation state.
  • Hand detached work to an explicit owner such as a narrow background-task port; do not leak
    ExecutionContext
    inward.
Completion criterion: Every inner signature uses application/domain types; each operation and factory has a current caller; and dependency, tracer, and side-effect lifetimes have explicit owners.
根据消费者需求定义契约:
ts
/** Persistence capability required by document application services. */
export interface DocumentStore {
  /** Find one document by its domain ID. */
  find(id: DocumentId): Promise<Document | null>;
  /** Persist one valid document. */
  save(document: Document): Promise<void>;
}

/** Exact dependencies of the publish-document use case. */
export type PublishDocumentDependencies = Readonly<{
  documents: DocumentStore;
  clock: Clock;
  tracer: Tracer;
}>;
规则:
  • 使用领域输入和已解析输出,而不是原始 key、字符串、请求对象或 binding 选项。
  • 只暴露实际调用方需要的操作。
  • 严格限定每个服务的依赖对象;绝不创建共享的万能依赖包,也不要将
    Env
    改名为
    Dependencies
    。
  • 将平台序列化、键空间、分页 token、TTL、元数据和输出解析放在绑定适配器中。
  • 由应用服务负责策略。适配器应准确转换失败,使服务能够区分可恢复情况、数据损坏和程序缺陷。
  • 向会创建 span 的服务和适配器注入
    Tracer
    ,但不要将其添加到每个方法签名中。
  • 仅当作用域在组合完成后确实动态变化时,才添加由应用拥有的工厂,例如身份验证后进行与账户绑定的构建。该工厂不得暴露
    Env
    或原始 binding。
明确选择生命周期:
  • 当 binding 和 tracer 可安全用于并发调用时,复用不可变适配器和服务。
  • 当依赖会保留请求范围内的运行时对象或可变调用状态时,在每次调用时构建。
  • 将分离任务移交给显式所有者,例如职责狭窄的后台任务端口;不要让
    ExecutionContext
    向内部泄漏。
完成标准: 每个内部签名都使用应用或领域类型;每项操作和工厂都有当前调用方;依赖、tracer 和副作用生命周期都有明确所有者。

3. Build or refactor

3. 构建或重构

New service branch

新服务分支

  1. Implement the application service against its ports without importing TanStack or Cloudflare runtime types.
  2. Implement one binding adapter per external role. Give technology-specific implementations explicit names, such as
    WorkersKvDocumentStore
    .
  3. Keep protocol concerns in the HTTP, RPC, queue, Workflow, or scheduled adapter.
  4. Supply the composed service to every operational entrypoint that serves the use case.
  1. 基于端口实现应用服务,不导入 TanStack 或 Cloudflare 运行时类型。
  2. 为每种外部角色实现一个绑定适配器。为技术特定的实现提供明确名称,例如
    WorkersKvDocumentStore
    。
  3. 将协议关注点保留在 HTTP、RPC、队列、Workflow 或定时适配器中。
  4. 向服务该用例的每个运行入口点提供组合后的服务。

Existing code branch

现有代码分支

  1. Preserve caller-visible behavior with tests at the current public seam where evidence is missing.
  2. Define the port at the direct consumer from the operations it already uses.
  3. Put the existing raw binding behind an adapter before changing policy.
  4. Replace the direct consumer's raw dependency with the port.
  5. Move adapter construction outward one layer at a time. Intermediate layers may pass the typed port, never both the port and raw binding as a permanent design.
  6. Split mixed modules: policy moves to the application service; platform mechanics move to the adapter; protocol mapping stays at the entrypoint.
  7. Remove obsolete
    Env
    parameters, factories, generic wrappers, duplicated parsers/key builders, and compatibility overloads after their final callers move.
Do not broaden the refactor to unrelated bindings merely for symmetry.
Completion criterion: New code is fully composed on every serving surface, or every migrated raw binding reaches only its adapter; behavior and policy changes are either absent or explicitly requested and tested.
  1. 如果缺少证据,先在当前公开接缝处添加测试,以保留调用方可见行为。
  2. 在直接消费者处,根据其已经使用的操作定义端口。
  3. 在更改策略之前,将现有原始 binding 置于适配器之后。
  4. 使用端口替换直接消费者的原始依赖。
  5. 逐层向外移动适配器构建逻辑。中间层可以传递类型化端口,但绝不能将端口和原始 binding 同时作为永久设计保留。
  6. 拆分混合模块:策略移至应用服务,平台机制移至适配器,协议映射保留在入口点。
  7. 在最后的调用方完成迁移后,移除过时的
    Env
    参数、工厂、通用包装器、重复的解析器或 key builder,以及兼容性重载。
不要仅为对称性而将重构扩展到无关的 binding。
完成标准: 新代码在每个服务面上都完成了组合,或者每个已迁移的原始 binding 都只能由其适配器访问;行为和策略要么保持不变,要么已明确提出要求并经过测试。

4. Compose every runtime surface

4. 组合每个运行时服务面

At each composition root, in order:
  1. Parse configuration and entrypoint props.
  2. Create or obtain the tracer.
  3. Establish correlation before invoking traced work.
  4. Wrap raw bindings in adapters, injecting the tracer where needed.
  5. Assemble exact service dependencies.
  6. Invoke the service through a thin protocol adapter.
  7. Map results and safe errors to the external contract.
For TanStack Start:
  • Build the graph in
    src/server.ts
    with one factory, such as
    createAppDeps(env, ctx)
    , for each fetch, queue, and scheduled invocation.
  • Pass it to the Start handler as request context, and declare its type through
    Register
    .
  • Server functions and middleware read exact capabilities from
    context.deps
    . They never read
    env
    or a binding name.
  • Inject the tracer used by root tracing middleware instead of creating unrelated tracers deeper in the app.
Select the composition root by dependency lifetime, not by a constructor or handler name. Reuse its dependency factory across operational entrypoints; do not build a separate graph inside each route or SDK callback.
Completion criterion: Every serving surface receives the dependency graph for its scope; no inner module acquires runtime resources or reads a binding name.
在每个组合根中,按以下顺序执行:
  1. 解析配置和入口点 props。
  2. 创建或获取 tracer。
  3. 在调用受追踪的工作之前建立关联。
  4. 将原始 binding 包装到适配器中,并在需要时注入 tracer。
  5. 组装精确的服务依赖。
  6. 通过轻量协议适配器调用服务。
  7. 将结果和可安全暴露的错误映射到外部契约。
对于 TanStack Start:
  • 在
    src/server.ts
    中构建依赖图,并使用单一工厂,例如
    createAppDeps(env, ctx)
    ,处理每次 fetch、队列和定时调用。
  • 将其作为请求上下文传递给 Start handler,并通过
    Register
    声明其类型。
  • Server functions 和 middleware 从
    context.deps
    读取精确能力,绝不读取
    env
    或 binding 名称。
  • 注入根追踪 middleware 所使用的 tracer,避免在应用更深层创建无关的 tracer。
根据依赖生命周期而非构造函数或 handler 名称选择组合根。 在各个运行入口点之间复用依赖工厂;不要在每个 route 或 SDK callback 内构建单独的依赖图。
完成标准: 每个服务面都获得其作用域内的依赖图;没有内部模块获取运行时资源或读取 binding 名称。

5. Verify the seam

5. 验证接缝

Verify through production seams:
  • application-service behavior with small recording fakes implementing real ports;
  • binding-adapter serialization, parsing, and failures against the representative local Cloudflare runtime;
  • entrypoint composition and external result/error projection;
  • span names, correlation, safe attributes, and detached-work ownership;
  • type checking, focused tests, architecture checks, and repository-required validation.
Search for leakage using the project's binding names and runtime types, for example:
bash
rg 'Env|KVNamespace|R2Bucket|D1Database|DurableObjectNamespace|ExecutionContext' src
Classify every match: composition root, binding adapter, unavoidable framework declaration, or violation. Also search for direct
context.env
and binding-name access in route, service, domain, and capability modules.
Completion criterion: Every raw platform reference is classified and permitted; every changed port, adapter, service, entrypoint, failure path, and lifecycle has evidence; and no application module imports or receives Cloudflare runtime types.
通过生产接缝进行验证:
  • 使用实现真实端口的小型记录型 fake 验证应用服务行为;
  • 在有代表性的本地 Cloudflare 运行时中验证绑定适配器的序列化、解析和失败处理;
  • 验证入口点组合以及外部结果和错误投影;
  • 验证 span 名称、关联、安全属性和分离任务所有权;
  • 执行类型检查、聚焦测试、架构检查以及仓库要求的验证。
使用项目的 binding 名称和运行时类型搜索泄漏,例如:
bash
rg 'Env|KVNamespace|R2Bucket|D1Database|DurableObjectNamespace|ExecutionContext' src
对每个匹配项进行分类:组合根、绑定适配器、不可避免的框架声明或违规。还要搜索 route、service、domain 和 capability 模块中对
context.env
及 binding 名称的直接访问。
完成标准: 每个原始平台引用都已分类并获得许可;每个变更过的端口、适配器、服务、入口点、失败路径和生命周期都有证据;并且没有应用模块导入或接收 Cloudflare 运行时类型。