Arquitectura en capas: responsabilidades y límites | Nicolás Garzón
La arquitectura en capas separa responsabilidades por nivel de abstracción. Funciona cuando las dependencias son claras y cada capa aporta una decisión; falla cuando solo distribuye un CRUD por archivos ceremoniales.
Una estructura común distingue:
Texto
Copiar presentación
→ aplicación
→ dominio
→ infraestructuraCada capa responde a preguntas diferentes:
Presentación: ¿cómo entra y sale información?
Aplicación: ¿qué intención se ejecuta y en qué orden?
Dominio: ¿qué reglas e invariantes deben conservarse?
Infraestructura: ¿cómo se conectan mecanismos concretos?
La arquitectura no mejora por añadir capas. Mejora cuando cada una reduce conocimiento y localiza cambios.
Sin separación, controllers, queries y reglas suelen mezclarse. Esto provoca:
Reglas duplicadas entre endpoints.
Modelos de base usados como contratos públicos.
Dificultad para probar casos de uso.
Cambios de transporte que afectan negocio.
Servicios gigantes con responsabilidades inconexas.
Las capas intentan ordenar el flujo y evitar que partes de alto nivel conozcan detalles innecesarios.
Conoce HTTP, GraphQL, CLI o UI. Valida forma, obtiene contexto autenticado y traduce resultados.
No debería decidir si un pedido puede cancelarse.
Obtiene recursos.
Verifica autorización contextual.
Invoca reglas.
Coordina persistencia y efectos.
Devuelve un resultado explícito.
No debería construir SQL ni formatear respuestas HTTP.
Modela reglas e invariantes. Puede incluir entidades, value objects, policies y servicios de dominio.
No debería depender del framework ni del ORM.
Implementa persistencia, brokers, proveedores, filesystem y mecanismos técnicos.
Puede depender de contratos de aplicación; estos no deberían importar sus tipos.
Una capa solo accede a la inmediatamente inferior.
Texto
Copiar presentation → application → domainAumenta disciplina, pero puede añadir forwarding innecesario.
Permite saltos controlados. Una query de lectura puede usar una proyección directamente desde aplicación sin reconstruir un agregado.
La elección debe evitar tanto el acoplamiento arbitrario como la ceremonia.
Texto
Copiar controller
→ service
→ repository
→ tableSi cada capa solo copia parámetros y devuelve el mismo objeto, no existe separación semántica.
TypeScript
Copiar class ProductService {
constructor ( private readonly repository: ProductRepository) { }
create ( dto: ProductDto) {
return this . repository. create ( dto) ;
}
} La clase añade navegación, no comportamiento ni protección.
TypeScript
Copiar class CreateProduct {
constructor (
private readonly products: ProductRepository,
private readonly skuPolicy: SkuUniquenessPolicy,
) { }
async execute ( command: CreateProductCommand) : Promise < CreateProductResult> {
if ( ! command. name. trim ( ) ) return { type: "invalid-name" } ;
const skuAvailable = await this . skuPolicy. isAvailable (
command. tenantId,
command. sku,
) ;
if ( ! skuAvailable) return { type: "duplicate-sku" } ;
const product = Product. create ( command) ;
await this . products. save ( product) ;
return { type: "created" , productId: product. id } ;
}
} La capa de aplicación coordina intención, política y resultado. El dominio protege creación. La infraestructura implementa almacenamiento.
Una estructura global por capas puede dispersar una feature:
Texto
Copiar controllers/order.ts
services/order.ts
repositories/order.ts
models/order.tsUn slice vertical agrupa lo que cambia junto:
Texto
Copiar orders/
├── confirm-order/
│ ├── controller.ts
│ ├── use-case.ts
│ ├── contract.ts
│ └── tests.ts
├── cancel-order/
└── domain/Los slices pueden contener capas internas. No son opuestos: una estructura saludable suele organizar primero por capacidad y luego por responsabilidad.
Texto
Copiar POST /orders/:id/confirm
→ controller crea command
→ ConfirmOrder coordina
→ Order protege transición
→ repository persiste
→ presenter mapea resultado
Presentación traduce invalid-state a 409.
Aplicación distingue ausencia de rechazo de negocio.
Infraestructura traduce timeout o constraint.
Dominio no conoce códigos HTTP.
Una arquitectura en capas tradicional puede permitir que negocio dependa de persistencia. Para proteger políticas se combina con inversión de dependencias:
Texto
Copiar application define repository port
infrastructure lo implementaPor eso “layered” no implica automáticamente Clean Architecture.
No toda lectura necesita dominio rico.
TypeScript
Copiar interface OrderSummaryQuery {
execute ( input: OrderSummaryInput) : Promise < OrderSummary> ;
} Una proyección puede consultar datos optimizados directamente. Debe respetar autorización y ownership, pero evita forzar modelos de escritura sobre lecturas.
La capa de aplicación suele definir el límite lógico. La infraestructura implementa el mecanismo.
Una transacción no debería abarcar llamadas de red lentas sin un análisis explícito, porque mantiene locks y recursos.
Puede usar presentación, servicio simple y repository sin dominio complejo. No inventes entidades ricas cuando no existen reglas.
Las capas globales pueden crear un DomainService gigante. Prefiere módulos y agregados por capacidad.
No necesitas luchar contra todas sus convenciones. Mantén reglas importantes fuera de handlers y modelos persistentes cuando aporte valor.
Un caso de uso puede coordinar módulos, pero no debe apropiarse de sus invariantes.
Capas que solo delegan.
Service layer con toda la lógica.
Dominio anémico.
Modelos ORM compartidos.
Presentación accediendo a DB.
Un directorio global por capa para todo el sistema.
Repositories genéricos.
Saltos arbitrarios que eliminan la separación.
Reglas de import.
Pruebas de casos de uso sin HTTP.
Tests de adapters con infraestructura real.
Revisión de cambios que atraviesan demasiadas capas.
Detección de servicios gigantes.
Control de modelos que cruzan límites.
Aplicaciones con flujos claros.
Necesidad de separar transporte, coordinación y reglas.
Equipos que comprenden responsabilidades.
Sistemas donde la estructura por módulos puede contener capas internas.
Features pequeñas: usa slices ligeros.
Lecturas complejas: permite proyecciones.
Dominio simple: evita capas vacías.
Sistema grande: organiza por contexto antes que por tipo técnico.
Una capa debe aportar responsabilidad, no forwarding.
Las capas organizan niveles de abstracción.
Layered no garantiza inversión de dependencias.
Vertical slices localizan cambios por feature.
Lecturas y escrituras pueden necesitar caminos distintos.
La profundidad debe corresponder al dominio.
¿Cómo reconoces una capa ceremonial?
¿Qué diferencia existe entre capa de aplicación y dominio?
¿Cuándo permitirías una capa abierta?
¿Por qué organizar todo globalmente por tipo puede reducir cohesión?
¿Cómo manejarías una query de dashboard?
Adapter Pattern estudia la traducción puntual que permite conectar contratos incompatibles sin contaminar al consumidor.