Makingcode
Volver al blog

NestJS Enterprise · Parte 3 de 4

Ver todos
Arquitectura4 min de lecturaAvailable in English

Tu API no necesita más servicios, necesita eventos

Si cada nueva funcionalidad te obliga a modificar cinco servicios distintos, probablemente tengas un problema de acoplamiento. Aprende cómo Event-Driven Architecture ayuda a desacoplar módulos y escalar aplicaciones NestJS.

Tu API no necesita más servicios, necesita eventos

Llega un momento en toda aplicación en crecimiento donde un simple caso de uso empieza a convertirse en una cadena de responsabilidades difícil de mantener.

Imagina el proceso de registro de usuarios.

Al principio parece sencillo:

await userRepository.create(user);

Meses después, el mismo flujo termina haciendo algo parecido a esto:

await userRepository.create(user);
 
await emailService.sendWelcomeEmail(user);
 
await auditService.registerUserCreation(user);
 
await analyticsService.trackNewUser(user);
 
await crmService.createContact(user);
 
await notificationService.notifyAdmins(user);

El problema no es el código.

El problema es que tu módulo de usuarios ahora conoce demasiadas cosas.

Sabe cómo enviar correos.

Sabe cómo registrar auditorías.

Sabe cómo actualizar métricas.

Sabe cómo integrarse con sistemas externos.

Cada nueva integración aumenta el acoplamiento.

Cada nuevo requerimiento obliga a modificar código que ya funcionaba.

Y eventualmente algo tan simple como registrar un usuario se convierte en una operación de alto riesgo.

Aquí es donde entra Event-Driven Architecture.

El problema real: dependencias invisibles

Cuando analizamos sistemas que se vuelven difíciles de mantener, normalmente encontramos el mismo patrón:

UserService
 ├── EmailService
 ├── AuditService
 ├── AnalyticsService
 ├── CRMService
 └── NotificationService

Cada dependencia adicional aumenta:

  • Complejidad
  • Acoplamiento
  • Tiempo de pruebas
  • Riesgo de regresiones

La consecuencia es que un cambio aparentemente pequeño termina afectando múltiples áreas del sistema.

La idea detrás de Event-Driven Architecture

En lugar de ejecutar todas las acciones directamente, el módulo publica un evento.

UserService


UserCreatedEvent

 ┌────┼────┬─────┐
 ▼    ▼    ▼     ▼
Email Audit CRM Analytics

El servicio ya no necesita conocer quién consume el evento.

Solo comunica algo que ocurrió.

this.eventBus.publish(
  new UserCreatedEvent(user.id, user.email),
);

Nada más.

Los eventos representan hechos

Un error común es modelar eventos como comandos.

Mal:

SendWelcomeEmailEvent

Bien:

UserCreatedEvent

La diferencia es importante.

Los eventos describen algo que ya ocurrió.

No expresan una intención.

Piensa en ellos como hechos del negocio.

UserCreatedEvent
 
OrderPaidEvent
 
InvoiceGeneratedEvent
 
SubscriptionCanceledEvent

Implementándolo en NestJS

NestJS incluye soporte nativo para eventos mediante CQRS.

Definimos el evento:

export class UserCreatedEvent {
  constructor(
    public readonly userId: string,
    public readonly email: string,
  ) {}
}

Publicamos el evento:

this.eventBus.publish(
  new UserCreatedEvent(
    user.id,
    user.email,
  ),
);

Creamos un handler:

@EventsHandler(UserCreatedEvent)
export class SendWelcomeEmailHandler
  implements IEventHandler<UserCreatedEvent>
{
  async handle(event: UserCreatedEvent) {
    await this.emailService.sendWelcomeEmail(
      event.email,
    );
  }
}

Ahora el módulo de usuarios ya no sabe nada sobre correos electrónicos.

Un evento, múltiples consumidores

Esta es una de las ventajas más importantes.

Un mismo evento puede ser consumido por varios módulos.

UserCreatedEvent
 
├── SendWelcomeEmailHandler
├── RegisterAuditHandler
├── SyncCRMHandler
└── GenerateMetricsHandler

Y el productor no necesita modificarse.

¿Cuándo usar eventos?

No todo debe convertirse en un evento.

Los eventos son especialmente útiles cuando:

  • Existen efectos secundarios.
  • Hay múltiples consumidores.
  • Los módulos evolucionan de forma independiente.
  • El proceso puede ejecutarse de forma asíncrona.

Por ejemplo:

✅ Registro de usuarios

✅ Creación de órdenes

✅ Generación de facturas

✅ Actualización de inventario

✅ Notificaciones

¿Cuándo NO usar eventos?

Si una operación es estrictamente necesaria para completar una transacción:

CreateOrder
  └── ValidateStock

No la conviertas en evento.

Forma parte del flujo principal.

Los eventos son excelentes para desacoplar.

No son una excusa para ocultar lógica crítica.

Lo que ganamos en la práctica

Beneficio Impacto
Menor acoplamiento Menos cambios cruzados
Escalabilidad Nuevos consumidores sin tocar el productor
Mantenibilidad Código más simple
Reutilización Un evento alimenta múltiples procesos
Evolución Integraciones independientes

Un patrón que encaja perfectamente con Arquitectura Hexagonal

Si ya aplicas Arquitectura Hexagonal, los eventos encajan de forma natural.

El dominio genera eventos.

La aplicación los publica.

Los adaptadores reaccionan.

Esto mantiene el núcleo de negocio limpio y libre de dependencias externas.

Para cerrar

Muchos equipos intentan resolver problemas de escalabilidad agregando más servicios.

La mayoría de las veces el problema no es la cantidad de servicios.

Es la cantidad de dependencias entre ellos.

Event-Driven Architecture permite que los módulos colaboren sin conocerse.

Y cuando un sistema deja de depender de llamadas directas para cada integración, la velocidad de desarrollo aumenta y el mantenimiento deja de convertirse en una pesadilla.

Si tu UserService envía correos, registra auditorías, actualiza métricas y sincroniza sistemas externos, probablemente no necesitas otro servicio.

Probablemente necesitas eventos.

Recibe artículos por email

Sin spam — solo un aviso cuando publique algo nuevo sobre backend, cloud y arquitectura.

Un email cuando salga un artículo. Puedes darte de baja cuando quieras.

Artículos relacionados