Skip to content

组合根

组合根是应用选择并连接具体实现的边界。领域代码声明需要什么,基础设施提供实现,InferDI 只出现在组装代码中。

领域契约

接口由领域层拥有,因为用例依赖它。这个文件不导入容器或框架。

ts
export interface UserStore {
  findName(id: string): Promise<string | undefined>
}

export class GetGreeting {
  constructor(private readonly users: UserStore) {}

  async execute(id: string) {
    const name = await this.users.findName(id)
    return name === undefined ? 'Hello, stranger' : `Hello, ${name}`
  }
}

基础设施实现

基础设施实现领域契约。真实适配器会调用数据库客户端;这个小例子把调用过程直接写出来。

ts
import type { UserStore } from './domain'

export class PostgresUserStore implements UserStore {
  constructor(private readonly dsn: string) {}

  async findName(id: string) {
    console.info(`query ${this.dsn} for ${id}`)
    return id === '42' ? 'Ada' : undefined
  }
}

组装应用

只有这个文件导入 InferDI。它选择 PostgresUserStore,传入 DSN,并把它连接到 GetGreeting

ts
import { Container } from '@inferdi/inferdi'
import { GetGreeting } from './domain'
import { PostgresUserStore } from './infrastructure'

export const container = new Container()
  .registerValue('dsn', 'postgres://localhost/app')
  .registerClass('users', PostgresUserStore, ['dsn'])
  .registerClass('greeting', GetGreeting, ['users'])

注册元组会与构造函数逐一核对。修改 GetGreetingPostgresUserStore 后,过期的组装代码会在这条边界报错。

在边界调用服务

HTTP 路由、CLI 命令或队列消费者解析顶层服务。业务操作仍通过普通方法调用。

ts
import { container } from './container'

export async function handleUser(id: string) {
  return container.get('greeting').execute(id)
}

如果操作需要请求或作业输入,就在这里创建子作用域,并在同一边界释放;也可以让框架适配器把它绑定到框架生命周期。

直接测试领域代码

单元测试无需构建容器。它把假的 UserStore 传给生产中使用的同一个 GetGreeting 类。

ts
import { GetGreeting, type UserStore } from './domain'

const fakeUsers: UserStore = {
  async findName(id) {
    return id === '42' ? 'Ada' : undefined
  }
}

const service = new GetGreeting(fakeUsers)
const result = await service.execute('42')

if (result !== 'Hello, Ada') {
  throw new Error(`Unexpected greeting: ${result}`)
}

集成测试需要运行真实组合图并替换一个实现时,可以使用 .override()。测试单个领域服务时,直接构造往往更清楚。接着阅读测试与覆盖