La arquitectura hexagonal (puertos y adaptadores) mantiene tus reglas de negocio en el centro y empuja los frameworks, bases de datos y colas a los bordes. En Node.js, eso significa que las rutas de Express y los clientes de Prisma nunca se filtran en el código de dominio.
El dominio contiene entidades, objetos de valor e interfaces de puerto (dependencias salientes como abstracciones)
La aplicación orquesta los casos de uso; una clase o función por historia de usuario
La infraestructura implementa puertos: rutas HTTP, repositorios Prisma, clientes S3, workers de BullMQ
Los adaptadores de conducción (HTTP, CLI) llaman a los servicios de la aplicación; los adaptadores controlados (DB, correo electrónico) son llamados a través de puertos
La dirección de la dependencia siempre apunta hacia adentro: la infraestructura depende de la aplicación/dominio, nunca al revés
Controladores gordos que se hacen pasar por adaptadores - Las rutas con 80 líneas de lógica de negocio no son hexagonales. Solución: Extrae un caso de uso; deja la ruta en 5-10 líneas.
Puertos que reflejan formas de ORM - save(prismaOrder) acopla el dominio a Prisma. Solución: Mapea entre entidades de dominio y DTOs de persistencia solo dentro del adaptador.
Un root de composición gigante - main.ts se convierte en 400 líneas de cableado. Solución: Fábricas registerBillingModule() por módulo que devuelven routers y servicios.
Omitir adaptadores en memoria - Los equipos solo escriben adaptadores de Postgres y omiten las pruebas rápidas. Solución: Envía InMemoryInvoiceRepository junto con el real.
Ceremonia hexagonal en una API de 3 rutas - Tres endpoints y un desarrollador no necesitan cuatro carpetas. Solución: Comienza con dominio + rutas; extrae puertos cuando aparezca un segundo adaptador.
¿Cuál es la diferencia entre un puerto y un adaptador?
Un puerto es una interfaz que tu aplicación define (NotificationPort). Un adaptador es la implementación concreta que se comunica con el mundo real (SendGridNotificationAdapter, InMemoryNotificationAdapter).
¿La arquitectura hexagonal requiere una clase por caso de uso?
No. Una función chargeInvoice(deps, input) funciona si las dependencias se pasan explícitamente. Las clases ayudan cuando los casos de uso llevan estado o usas un contenedor DI.
¿Dónde pertenece la validación de Zod?
En el límite del adaptador HTTP. Analiza req.body en un DTO tipado, luego pasa objetos planos al caso de uso. La validación de dominio cubre invariantes que Zod no puede expresar (por ejemplo, "la factura no debe ser cobrada dos veces").
¿Puedo usar el diseño hexagonal con NestJS?
Sí. Los proveedores de Nest implementan puertos; los controladores son adaptadores de conducción. Mantén las carpetas de dominio libres de @Injectable() si quieres pruebas unitarias sin framework.
¿Cómo comparto puertos entre módulos?
Prefiere los puertos locales del módulo. Si dos módulos necesitan la misma abstracción, mueve el puerto a shared/ports/ solo cuando exista un segundo consumidor; evita los kernels compartidos prematuros.
¿Los workers de cola deberían ser adaptadores?
Sí. Un consumidor de BullMQ es un adaptador de conducción que deserializa una carga útil de trabajo y llama al mismo caso de uso que tu ruta HTTP.
¿Cuántos puertos por módulo son demasiados?
Si cada llamada externa tiene su propio puerto, es posible que estés sobre-abstraiendo. Comienza con repositorios y gateways que esperas intercambiar o simular en las pruebas.
¿Esto funciona con Prisma?
Envuelve Prisma en un adaptador de repositorio. Nunca exportes PrismaClient desde las capas de dominio o aplicación.
¿Cómo migro un archivo de servicio "god-service" incrementalmente?
Extrae un caso de uso y un puerto a la vez. Deja las rutas heredadas llamando al código antiguo hasta que se pruebe la nueva ruta, luego elimina el bloque antiguo.
¿Es hexagonal lo mismo que DDD?
Relacionado pero no idéntico. Hexagonal se trata de la dirección de la dependencia; DDD agrega contextos delimitados, agregados y lenguaje ubicuo. Puedes usar hexagonal sin DDD completo.