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