Clean Architecture en aplicaciones de negocio con Node.js
Cómo aplicar los principios de Clean Architecture en aplicaciones Node.js que manejan lógica de negocio real, con ejemplos prácticos.
Por qué la arquitectura importa en aplicaciones de negocio
Las aplicaciones que dan soporte a operaciones de negocio reales tienen características que las distinguen de los proyectos pequeños:
- La lógica de negocio es compleja y cambia con frecuencia.
- Hay múltiples integraciones con sistemas externos.
- El equipo crece y varias personas trabajan en el mismo código.
- Los errores tienen impacto real en las operaciones.
En este contexto, una arquitectura bien definida no es un lujo, es una necesidad.
Los principios de Clean Architecture aplicados a Node.js
Clean Architecture, popularizada por Robert C. Martin, propone organizar el código en capas con dependencias que siempre apuntan hacia el centro:
Frameworks & Drivers (Express, TypeORM, Redis...)
↓
Interface Adapters (Controllers, Presenters, Gateways)
↓
Application Business Rules (Use Cases)
↓
Enterprise Business Rules (Entities, Domain)
La regla fundamental: el dominio no depende de nada externo.
Estructura de directorios
Una estructura que funciona bien en proyectos Node.js reales:
src/
domain/
entities/
repositories/ # interfaces, no implementaciones
services/ # domain services
application/
use-cases/
ports/ # interfaces de entrada y salida
infrastructure/
persistence/ # implementaciones de repositorios
http/ # Express controllers
external/ # clientes de APIs externas
shared/
errors/
types/
Ejemplo: un caso de uso de pedido
// domain/entities/Order.ts
export class Order {
constructor(
public readonly id: string,
public readonly customerId: string,
public readonly items: OrderItem[],
public status: OrderStatus
) {}
confirm(): void {
if (this.status !== OrderStatus.DRAFT) {
throw new Error('Only draft orders can be confirmed');
}
this.status = OrderStatus.CONFIRMED;
}
}
// application/use-cases/ConfirmOrder.ts
export class ConfirmOrderUseCase {
constructor(
private readonly orderRepository: OrderRepository,
private readonly eventBus: EventBus
) {}
async execute(orderId: string): Promise<void> {
const order = await this.orderRepository.findById(orderId);
if (!order) throw new OrderNotFoundError(orderId);
order.confirm();
await this.orderRepository.save(order);
await this.eventBus.publish(new OrderConfirmedEvent(order));
}
}
Lo que más cuesta implementar bien
La separación entre dominio e infraestructura
Es fácil filtrar dependencias de infraestructura al dominio. Un import de un ORM en una entidad parece inofensivo hasta que el dominio está acoplado a la base de datos y los tests se vuelven imposibles.
La granularidad de los casos de uso
¿Un caso de uso por operación o por flujo? En la práctica, un caso de uso por operación de negocio funciona mejor. Es más fácil de testear y de mantener.
La gestión de errores
Los errores de dominio (lógica de negocio) son distintos de los errores de infraestructura (red, base de datos). Mezclarlos genera código difícil de manejar.
Cuándo no aplicar Clean Architecture
No todo proyecto necesita esta arquitectura. Para scripts, utilidades o aplicaciones pequeñas con poca lógica de negocio, el overhead no vale la pena.
Clean Architecture tiene sentido cuando:
- La lógica de negocio es el core del sistema.
- El sistema va a crecer y a ser mantenido por un equipo.
- La testeabilidad es importante.
Conclusión
Clean Architecture en Node.js no es un patrón rígido que hay que seguir al pie de la letra. Es un conjunto de principios que ayudan a mantener la lógica de negocio limpia y desacoplada de los detalles de implementación.
La clave es entender por qué cada capa existe y qué problema resuelve, no seguir la estructura mecánicamente.