Skip to content

Raíz de composición

La raíz de composición es el límite donde la aplicación elige y conecta implementaciones concretas. El dominio declara lo que necesita, infraestructura aporta una implementación e InferDI solo aparece en el código de ensamblado.

Contrato de dominio

El dominio posee la interfaz porque el caso de uso depende de ella. Este archivo no importa el contenedor ni el framework.

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}`
  }
}

Implementación de infraestructura

Infraestructura implementa el contrato del dominio. Un adaptador real usaría un cliente de base de datos; el ejemplo pequeño deja visible la llamada.

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
  }
}

Composición de la aplicación

Solo este archivo importa InferDI. Elige PostgresUserStore, le entrega el DSN y lo conecta con 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'])

La tupla de cada registro se comprueba contra su constructor. Al cambiar GetGreeting o PostgresUserStore, el cableado obsoleto falla en este límite.

Usar el servicio en el límite

Una ruta HTTP, un comando CLI o un consumidor de cola resuelve el servicio superior. La operación de negocio sigue usando un método normal.

ts
import { container } from './container'

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

Abre aquí un scope hijo cuando la operación necesite entradas de una petición o trabajo. Libéralo en el mismo límite o deja que un adaptador lo ate al ciclo de vida del framework.

Probar el dominio directamente

La prueba unitaria no construye un contenedor. Pasa un UserStore falso a la misma clase GetGreeting utilizada en producción.

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}`)
}

Usa .override() en pruebas de integración que ejecuten el grafo real con una implementación sustituida. Para un solo servicio de dominio, la construcción directa suele ser más clara. Continúa con Pruebas y overrides.