No Pongas Todo en un Solo Lugar: Introducción a Microservicios con NestJS

Guía de Microservicios NestJS De Monolito a API Gateway

1. Introducción

¡Has llegado al gran final de nuestra serie! A lo largo de este viaje, hemos construido una aplicación impresionante. Empezamos con un CRUD simple y la convertimos en una API robusta, segura, con relaciones complejas, probada y que incluso habla GraphQL. Hemos construido una aplicación “monolítica” de manual, y lo decimos como un cumplido: todo está perfectamente organizado, modularizado y contenido en un solo proyecto.

Pero, ¿qué sucede cuando tu aplicación tiene un éxito masivo? ¿Qué pasa cuando tu monolito, que era tan simple de gestionar, ahora tiene cientos de funcionalidades y un equipo de 50 desarrolladores trabajando en él?

Empiezas a enfrentar el dilema del crecimiento:

  • Despliegues Lentos y Arriesgados: Un pequeño cambio en el módulo de Tareas requiere volver a probar y desplegar toda la aplicación.
  • Escalado Ineficiente: Quizás tu servicio de autenticación recibe 100 veces más tráfico que el de tareas. En un monolito, no puedes escalar solo el AuthModule; tienes que desplegar 10 copias de la aplicación completa, desperdiciando recursos.
  • Equipos que se “Pisan”: Varios equipos trabajando sobre la misma base de código pueden generar conflictos y cuellos de botella.

La solución a estos problemas de escala es una arquitectura de Microservicios. Este es un enfoque para construir una sola aplicación como un conjunto de servicios pequeños, independientes, especializados y que se comunican entre sí.

¿Y por qué NestJS? Porque NestJS fue construido desde su núcleo pensando en esta arquitectura. Proporciona “Transporters” (como TCP, RabbitMQ, Kafka) que actúan como el sistema nervioso, permitiendo que estos servicios independientes hablen entre sí de manera fluida y eficiente.

Nuestro objetivo: En esta guía final, no solo aprenderemos la teoría. Vamos a tomar nuestra aplicación monolítica y realizaremos una “cirugía” arquitectónica. Extraeremos nuestro AuthModule y UserModule para convertirlos en un microservicio de autenticación completamente independiente. Nuestra aplicación principal se transformará en un API Gateway, convirtiéndose en la única puerta de entrada para los clientes y consumiendo nuestro nuevo servicio interno.


Hemos establecido el desafío final. Antes de empezar a escribir código, es crucial entender los pros y contras de esta arquitectura.

2. Monolito vs. Microservicios: ¿Por Qué y Cuándo?

Elegir entre un monolito y microservicios es una de las decisiones de arquitectura más importantes que tomarás. No hay una respuesta “correcta”, solo hay “la respuesta correcta para tu contexto”.


El Monolito (Lo que tenemos ahora) 🏛️

Un monolito es una aplicación donde toda la funcionalidad (autenticación, tareas, etiquetas, GraphQL) reside en una única base de código, se construye como una sola unidad y se despliega como un solo proceso.

Pros (Ventajas):

  • Simplicidad Inicial: Es mucho más fácil empezar. La configuración, el desarrollo y las pruebas (como vimos) son sencillos porque todo está en un solo lugar.
  • Rendimiento: La comunicación entre módulos (ej. TasksService llamando a AuthService) es una simple llamada a una función en memoria, lo cual es extremadamente rápido.
  • Facilidad de Despliegue (al inicio): Solo tienes un “paquete” que desplegar en un servidor.

Cons (Desventajas a Escala):

  • Escalado Ineficiente: Este es el mayor problema. Si tu módulo de Auth recibe 1000 peticiones por segundo, pero tu módulo de Tasks solo recibe 10, no puedes escalar solo el módulo de Auth. Estás obligado a desplegar 10 copias de la aplicación completa, desperdiciando recursos.
  • Despliegues Arriesgados: Un pequeño cambio en el TagsModule (módulo de etiquetas) requiere un redespliegue de toda la aplicación, arriesgando la funcionalidad de Auth y Tasks.
  • Acoplamiento Tecnológico: Estás atado a una sola pila tecnológica (en nuestro caso, todo Node.js).

La Arquitectura de Microservicios 🌐

Es un enfoque arquitectónico donde una aplicación se construye como un conjunto de servicios pequeños e independientes. Cada servicio se ejecuta en su propio proceso, está construido en torno a una capacidad de negocio (ej. “Autenticación”) y se comunica a través de mecanismos ligeros (como TCP, gRPC o colas de mensajes).Imagen de microservices architecture diagram

Shutterstock

Pros (Ventajas):

  • Escalado Independiente: ¡Puedes desplegar 10 copias del servicio de Auth y solo 2 del servicio de Tasks! Escalas quirúrgicamente solo lo que necesitas.
  • Despliegue Independiente: El equipo de Auth puede desplegar actualizaciones varias veces al día sin que el equipo de Tasks se entere.
  • Resiliencia: Si el servicio de Tasks falla, la Autenticación puede seguir funcionando, permitiendo a los usuarios, por ejemplo, seguir gestionando su perfil.
  • Equipos Autónomos: Permite que equipos pequeños sean dueños de sus servicios de principio a fin.

Cons (Desventajas):

  • Complejidad Operacional: Ahora tienes 5 o 10 servicios que desplegar, monitorear y gestionar, en lugar de uno.
  • Latencia de Red: La comunicación entre servicios ya no es una llamada en memoria; es una llamada de red, que es inherentemente más lenta.
  • Complejidad de Desarrollo: Tienes que manejar fallos de red, consistencia de datos entre servicios y un sistema de comunicación distribuido.

El Patrón “API Gateway” (Nuestro Nuevo Monolito) 🚪

Entonces, si tenemos 5 microservicios, ¿cómo sabe el cliente (nuestra app móvil o web) a cuál llamar? ¿Guarda 5 URLs diferentes?

No. La solución es el patrón API Gateway.

El API Gateway es un servidor que actúa como la única puerta de entrada para todas las peticiones de los clientes. Nuestra aplicación monolítica actual se convertirá en este Gateway.

Sus responsabilidades serán:

  1. Recibir todas las peticiones HTTP/GraphQL del mundo exterior.
  2. Manejar la Autenticación: Validará el JWT del cliente (ya que es una tarea que todo endpoint necesita).
  3. Enrutar: Actuará como un “director de tráfico”, reenviando la petición al microservicio interno correcto (ej. la petición de login va al auth-service).
  4. Agregar Respuestas: Recibirá las respuestas de los microservicios y las enviará de vuelta al cliente.

Para el cliente, todo sigue pareciendo un monolito. Pero por dentro, hemos construido un sistema distribuido y escalable.


Ahora que entendemos la teoría, necesitamos entender cómo se “hablarán” estos servicios.

3. Los “Transporters” de NestJS: El Sistema Nervioso 🧠

Si los microservicios son los órganos (cerebro, corazón, pulmones), los transporters son el sistema nervioso que los conecta, permitiéndoles enviar y recibir señales.

Cuando el TasksService está en el mismo proyecto que el AuthService, la comunicación es una simple llamada a una función de JavaScript en memoria. Es instantánea. Pero cuando el AuthService vive en un servidor completamente diferente, ¿cómo le “habla” nuestra aplicación principal?

Podríamos usar HTTP (hacer una petición POST a http://auth-service/login), pero a menudo es ineficiente para la comunicación interna. Es un protocolo “pesado”, con cabeceras, métodos, códigos de estado, etc., diseñado para la web pública. Para la comunicación interna, queremos algo más rápido y ligero.

Aquí es donde entran los Transporters de NestJS. Son la capa de comunicación que abstrae el mecanismo de transporte subyacente. NestJS tiene soporte nativo para varios:

  • TCP: El más simple. Establece una conexión directa de bajo nivel entre dos servicios.
  • RabbitMQ / Kafka / NATS: Son Message Brokers (intermediarios de mensajes). Son más complejos pero increíblemente potentes.
  • gRPC: Un framework de alto rendimiento de Google, ideal para comunicación interna.

Patrones de Comunicación

Para elegir un transporter, primero debes entender el patrón de comunicación que necesitas. Hay dos tipos principales:

1. Petición-Respuesta (Síncrono)

Es como una llamada telefónica 📞.

  • Proceso: El Servicio A (nuestro Gateway) envía un mensaje al Servicio B (el auth-service) y espera activamente una respuesta. No puede continuar hasta que el Servicio B le responda.
  • Cuándo usarlo: Ideal para operaciones que el cliente está esperando, como un login, obtener un perfil o crear una tarea. El usuario necesita la respuesta para continuar.
  • Transporter Común: TCP es perfecto para este patrón.

2. Basado en Eventos (Asíncrono)

Es como enviar un correo electrónico 📧.

  • Proceso: El Servicio A (nuestro Gateway) “dispara un evento” (ej. usuario_creado) y no espera una respuesta. Simplemente sigue con su vida. Uno o más servicios (como un email-service o un analytics-service) están “escuchando” ese evento y reaccionan cuando lo reciben, en segundo plano.
  • Cuándo usarlo: Ideal para tareas que no bloquean al usuario. Por ejemplo, después de registrarse, el Gateway puede responder “¡Usuario creado!” inmediatamente, y al mismo tiempo disparar un evento para que el email-service envíe un correo de bienvenida 5 segundos después.
  • Transporters Comunes: RabbitMQ o Kafka son los reyes de este patrón.

Nuestra Elección: Empezar con TCP

Para este tutorial, usaremos TCP. ¿Por qué? Porque es el más simple de configurar, no requiere instalar ningún software de terceros (como un servidor de RabbitMQ) y es perfecto para demostrar el patrón de petición-respuesta que necesitamos para migrar nuestra lógica de autenticación.

Nos permitirá centrarnos en la arquitectura del microservicio sin complicarnos (aún) con la configuración de un broker de mensajería.


Ya sabemos el “por qué” (escalabilidad), y el “cómo” (Transporters TCP). Es hora de la cirugía.

4. Manos a la Obra: Creando el Microservicio de Autenticación

Este proceso implica crear un proyecto NestJS completamente nuevo y migrar nuestra lógica de autenticación a él.


4.1. Crear un Nuevo Proyecto NestJS

Primero, fuera de tu proyecto monolítico actual, usa el CLI de NestJS para crear una nueva aplicación. Esta será nuestro microservicio.

Bash

# Asegúrate de estar en tu carpeta de desarrollo, NO dentro del proyecto anterior
nest new auth-service

4.2. Migrar el Código y las Dependencias

Este es un paso de “copiar y pegar”, pero fundamental.

  1. Copiar los Módulos: Copia las carpetas src/auth y src/users (si la entidad User está en su propio módulo) de tu proyecto monolítico al directorio src/ del nuevo proyecto auth-service.
  2. Limpiar: En el auth-service, puedes eliminar los archivos app.controller.ts, app.service.ts y app.controller.spec.ts que vienen por defecto. No los necesitaremos.
  3. Instalar Dependencias: Tu nuevo auth-service necesita todas las dependencias que usaban tus módulos de autenticación. Abre el package.json del nuevo proyecto y añade:Bashnpm install @nestjs/microservices @nestjs/passport passport passport-jwt @nestjs/jwt bcrypt typeorm @nestjs/typeorm pg class-validator class-transformer # (Añade cualquier otra dependencia que tus módulos necesiten, ej: postgres 'pg' o sqlite)
  4. Configurar Módulos: Asegúrate de que el AppModule en src/app.module.ts de tu auth-service importe los módulos AuthModule, UserModule y el TypeOrmModule.forRoot(...) (necesita su propia conexión a la base de datos, que probablemente será la misma que usaba el monolito).

4.3. Adaptar el main.ts para que sea un Microservicio

Esta es la parte más importante. No queremos que este servicio escuche peticiones HTTP en el puerto 3000. Queremos que escuche mensajes TCP.

Reemplaza el contenido de src/main.ts en tu auth-service con esto:

TypeScript

// auth-service/src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Transport } from '@nestjs/microservices';

async function bootstrap() {
  // Creamos el microservicio en lugar de una app web
  const app = await NestFactory.createMicroservice(AppModule, {
    transport: Transport.TCP,
    options: {
      host: 'localhost',
      port: 3001, // Usaremos un puerto diferente al del gateway
    },
  });

  // El método listen() inicia el microservicio
  await app.listen();
  console.log('Auth microservice is listening on port 3001');
}
bootstrap();

4.4. Reemplazar Controladores con Patrones de Mensajes

Los microservicios TCP no entienden de rutas HTTP como POST /auth/login. Entienden patrones de mensajes.

Tenemos que modificar nuestro AuthController para que escuche mensajes en lugar de rutas.

En auth-service/src/auth/auth.controller.ts:

  • Antes (en el monolito):TypeScriptimport { Controller, Post, Body } from '@nestjs/common'; @Controller('auth') export class AuthController { @Post('/signup') signUp(@Body() authCredentialsDto: AuthCredentialsDto) { ... } @Post('/login') signIn(@Body() authCredentialsDto: AuthCredentialsDto) { ... } }
  • Ahora (en el microservicio):TypeScriptimport { Controller } from '@nestjs/common'; import { MessagePattern, Payload } from '@nestjs/microservices'; @Controller() // El decorador @Controller sigue siendo útil para la inyección de dependencias export class AuthController { // Reemplazamos @Post por @MessagePattern @MessagePattern({ cmd: 'signup' }) signUp(@Payload() authCredentialsDto: AuthCredentialsDto) { return this.authService.signUp(authCredentialsDto); } @MessagePattern({ cmd: 'login' }) signIn(@Payload() authCredentialsDto: AuthCredentialsDto) { return this.authService.signIn(authCredentialsDto); } }

Puntos Clave:

  • @MessagePattern({ cmd: 'signup' }): Le dice a NestJS que ejecute este método cuando reciba un mensaje TCP cuyo contenido (payload) sea un objeto con la propiedad cmd igual a 'signup'.
  • @Payload(): Es el decorador equivalente a @Body(), extrae los datos que vienen en el mensaje.

¡Lo has logrado! Si ejecutas npm run start:dev en tu auth-service, ahora tienes un servidor independiente que no es accesible vía HTTP, sino que está escuchando silenciosamente en el puerto 3001, esperando recibir patrones de mensajes.

Hemos construido la isla. Ahora, necesitamos que nuestro continente (el monolito) sepa cómo enviarle un barco.

5. Refactorizando el Monolito a un API Gateway

Este es el paso donde conectamos los dos mundos. Nuestra aplicación principal, que actualmente maneja las peticiones HTTP de los clientes, aprenderá a comunicarse con el auth-service interno a través de TCP.

Paso 1: Limpiando la Aplicación Principal

Primero, debemos eliminar la lógica que hemos migrado. En tu proyecto principal (el monolito):

  1. Elimina las Carpetas: Borra las carpetas src/auth y src/users (o donde residiera tu entidad User).
  2. Limpia el AppModule: Entra en src/app.module.ts y elimina AuthModule y UserModule de la lista de imports.
  3. Desinstala Dependencias (Opcional): Puedes desinstalar los paquetes que ya no se usan directamente en el gateway, como bcrypt. Sin embargo, passport-jwt y @nestjs/passport deben permanecer, como veremos en el siguiente paso.

Paso 2: El ClientsModule – El “Teléfono” del Gateway

Para que nuestro Gateway pueda “llamar” al microservicio, usamos el ClientsModule de NestJS.

Vamos a crear un nuevo AuthModule ligero dentro del Gateway. Su única responsabilidad será gestionar la comunicación con el auth-service.

  1. Crea una nueva carpeta src/auth en el Gateway.
  2. Dentro, crea src/auth/auth.module.ts:

TypeScript

// gateway/src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'AUTH_SERVICE', // Un token de inyección único
        transport: Transport.TCP,
        options: {
          host: 'localhost',
          port: 3001, // El puerto donde escucha nuestro auth-service
        },
      },
    ]),
  ],
  // Exportamos ClientsModule para que otros módulos puedan inyectar el cliente
  exports: [ClientsModule],
})
export class AuthModule {}

No olvides importar este nuevo AuthModule en tu src/app.module.ts.

Paso 3: Inyectando el ClientProxy

El ClientsModule nos da un proveedor llamado ClientProxy, que es el objeto que usamos para enviar mensajes. Lo inyectaremos en los controladores o servicios que necesiten comunicarse.

Vamos a recrear nuestro AuthController en el Gateway. Este controlador manejará rutas HTTP.

TypeScript

// gateway/src/auth/auth.controller.ts
import { Controller, Post, Body, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { AuthCredentialsDto } from './dto/auth-credentials.dto'; // (Copia los DTOs que necesites)

@Controller('auth')
export class AuthController {
  constructor(
    @Inject('AUTH_SERVICE') private readonly authClient: ClientProxy,
  ) {}

  @Post('/signup')
  signUp(@Body() authCredentialsDto: AuthCredentialsDto) {
    // En lugar de llamar a un servicio local, enviamos un mensaje
    return this.authClient.send({ cmd: 'signup' }, authCredentialsDto);
  }

  @Post('/login')
  signIn(@Body() authCredentialsDto: AuthCredentialsDto) {
    // 'send' devuelve un Observable, que NestJS maneja automáticamente
    return this.authClient.send({ cmd: 'login' }, authCredentialsDto);
  }
}
  • @Inject('AUTH_SERVICE'): Inyectamos el cliente que registramos en el módulo.
  • this.authClient.send(...): Este es el método clave. Envía el patrón de mensaje ({ cmd: 'login' }) y el payload (authCredentialsDto) al microservicio. Luego, espera la respuesta (el token) y la devuelve al cliente HTTP.

¡Y listo! Acabas de conectar el Gateway al microservicio. Las peticiones HTTP que lleguen a POST /auth/login serán ahora reenviadas vía TCP al auth-service, que procesará la lógica y devolverá el token.

Pero falta una pieza crítica: ¿cómo protegemos nuestras otras rutas, como /tasks?

6. Manejando la Autenticación a través del Gateway

Acabamos de mover la creación de usuarios y tokens a un servicio separado. Pero, ¿qué pasa con la validación de esos tokens? ¿Qué pasa con nuestro AuthGuard que protege el TasksController?

Este es un punto crucial: ¿dónde debe vivir la lógica de autenticación?

La respuesta se divide en dos partes:

  1. El Microservicio de Autenticación (auth-service) es el “emisor de pasaportes” 🛂. Su única responsabilidad es verificar las credenciales una vez (en el login) y emitir un pasaporte seguro (el JWT).
  2. El API Gateway (nuestra app principal) es el “guardia de frontera” 💂. Es responsable de revisar el pasaporte (validar el JWT) en cada petición que llega a rutas protegidas (como /tasks).

La JwtStrategy y el AuthGuard Viven en el Gateway

El TasksController (que maneja HTTP) sigue viviendo en nuestro Gateway. Por lo tanto, el AuthGuard que lo protege debe permanecer en el Gateway.

Esto es, de hecho, mucho más eficiente. Recordemos cómo funciona un JWT:

  • El token (pasaporte) está “firmado” digitalmente por el auth-service usando una clave secreta.
  • El token contiene el payload (información del usuario, como el id y el username).

Para validar el token, el Gateway (el guardia de frontera) no necesita hablar con el auth-service. Solo necesita:

  1. Tener la misma clave secreta que el auth-service usó para firmar el token.
  2. Verificar la firma del token. Si es válida, confía en el payload que contiene.

Implementación

  1. Restaurar el AuthModule (Ligero) en el Gateway:
    • Además de la configuración del ClientsModule, nuestro AuthModule en el Gateway también debe configurar PassportModule y JwtModule.
    • Necesitará la JwtStrategy (la puedes copiar de vuelta desde el microservicio).
    • Es crítico que el secret usado en el JwtModule del Gateway sea exactamente el mismo que el del auth-service. (¡Esto es un caso de uso perfecto para las variables de entorno!).
    TypeScript// gateway/src/auth/auth.module.ts import { Module } from '@nestjs/common'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { PassportModule } from '@nestjs/passport'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt.strategy'; // ¡La estrategia vive aquí! import { AuthController } from './auth.controller'; @Module({ imports: [ ClientsModule.register([ { name: 'AUTH_SERVICE', transport: Transport.TCP, options: { host: 'localhost', port: 3001 }, }, ]), PassportModule.register({ defaultStrategy: 'jwt' }), JwtModule.register({ secret: 'miPalabraSecretaSuperSegura123', // ¡Debe ser la misma que en el auth-service! signOptions: { expiresIn: 3600 }, }), ], providers: [JwtStrategy], // La estrategia es un provider local controllers: [AuthController], // El controlador HTTP exports: [JwtStrategy, PassportModule], // Exportar para que otros módulos (Tasks) lo usen }) export class AuthModule {}
  2. El Flujo de Petición (Ej: GET /tasks)
    1. Llega una petición a GET /tasks al Gateway con un Authorization: Bearer <token>.
    2. El AuthGuard del Gateway se activa.
    3. Llama a la JwtStrategy (local) del Gateway.
    4. La estrategia valida el token usando la clave secreta compartida.
    5. Si es válido, extrae el payload (ej. { userId: '...', username: '...' }) y adjunta el user al objeto request.
    6. La petición llega finalmente al TasksController (del Gateway).
    7. El TasksController ahora tiene el objeto user y puede, por ejemplo, pasar el userId al tasks-service (que sería otro microservicio) para filtrar las tareas.

Con esta arquitectura, el Gateway maneja toda la seguridad pública y el auth-service se enfoca únicamente en la lógica de identidad, creando un sistema desacoplado, seguro y eficiente.


Hemos completado la migración. Ahora, consolidemos este conocimiento tan avanzado.

7. Preguntas y Respuestas (FAQ)

1. ¿Cuándo es el momento adecuado para pasar de un monolito a microservicios?

Esta es la “pregunta del millón”. La respuesta es: no antes de que sea necesario. Empezar con un monolito casi siempre es lo correcto. Es más rápido de desarrollar y más fácil de desplegar. Deberías considerar los microservicios solo cuando empieces a sentir “dolores” reales de crecimiento, como:

  • Necesitas escalar una parte de tu aplicación de forma independiente.
  • Los despliegues de toda la aplicación se vuelven lentos y arriesgados.
  • Tu equipo de desarrollo ha crecido tanto que diferentes equipos “chocan” al trabajar en la misma base de código.

2. ¿TCP vs. RabbitMQ/Kafka? ¿Cuándo usar cada uno?

  • TCP: Úsalo para comunicación síncrona (Petición-Respuesta) simple. Es como una llamada telefónica: preguntas algo y esperas una respuesta. Es ideal para cosas como el login o pedir datos que el usuario está esperando activamente.
  • RabbitMQ/Kafka (Brokers de Mensajería): Úsalos para comunicación asíncrona (Basada en Eventos). Es como enviar un email: disparas un evento (ej. usuario_creado) y no esperas respuesta. Es perfecto para tareas en segundo plano (enviar emails de bienvenida, procesar análisis, etc.) y para comunicar un evento a múltiples servicios a la vez.

3. ¿Cómo se manejan los errores entre servicios?

Cuando un microservicio falla (ej. el auth-service no puede encontrar un usuario), no puede lanzar una HttpException, porque el Gateway es el único que habla HTTP. En su lugar, NestJS usa una clase llamada RpcException. El microservicio lanza una RpcException y el Gateway, al recibir este error, lo transforma automáticamente en una HttpException (como un 500 o 404) para el cliente final.


4. ¿Cómo comparto código (como DTOs o Entidades) entre diferentes microservicios?

Esta es una gran pregunta. Tienes dos microservicios separados, pero ambos necesitan conocer el AuthCredentialsDto. ¿Copias y pegas el archivo? Sería una pesadilla de mantenimiento. La solución estándar en la industria es usar un Monorepo. Un monorepo es un solo repositorio de código que contiene múltiples proyectos (tu Gateway, tu auth-service, tu tasks-service) y una librería compartida (libs/common) donde vivirían los DTOs y las definiciones de entidades.


5. ¿Esto no es más lento que un monolito?

Sí, lo es. Una llamada de red (TCP), por rápida que sea, siempre será miles de veces más lenta que una llamada a una función en memoria dentro de un monolito. Esto se conoce como latencia de red. El objetivo de los microservicios no es hacer la aplicación más rápida (aunque puede percibirse así si se escala correctamente), sino hacerla más escalable, resiliente y mantenible a gran escala. Es un trade-off (intercambio) consciente: sacrificas un poco de velocidad de comunicación interna para ganar una enorme flexibilidad arquitectónica.


8. Puntos Relevantes a Recordar

Si debes quedarte con cinco ideas clave de esta guía de arquitectura, que sean estas:

  1. Los Microservicios Resuelven Problemas de Escala: Son la solución a problemas de escalado independiente, despliegues autónomos y organización de equipos grandes. No son una solución para la simplicidad del desarrollo inicial; para eso está el monolito.
  2. El API Gateway es la Fachada: El cliente final (web/móvil) solo habla con el API Gateway. Este maneja todo el tráfico HTTP, la seguridad pública (Guards) y actúa como un director de orquesta, enrutando las peticiones a los servicios internos correctos.
  3. Transporters vs. HTTP: Los microservicios internos no se comunican entre sí usando HTTP. Utilizan Transporters (como TCP o RabbitMQ) que son más rápidos y ligeros, diseñados para la comunicación entre servidores.
  4. @MessagePattern es el Nuevo @Controller: En un microservicio, la lógica no se expone a través de rutas HTTP (@Post, @Get), sino a través de Patrones de Mensajes (@MessagePattern) que el Transporter sabe cómo escuchar.
  5. La Autenticación se Divide: La creación de identidad (login, signup, crear JWTs) vive en el microservicio de autenticación. La verificación de identidad (validar JWTs con un Guard) vive en el API Gateway, protegiendo las rutas públicas.

¡Ha sido un viaje increíble! Hemos construido una aplicación desde cero y la hemos hecho evolucionar a través de cinco etapas cruciales de desarrollo profesional.

9. Conclusión

¡Lo has logrado! Has completado el viaje, y qué viaje ha sido. Empezaste con los fundamentos de un CRUD y has llegado a diseñar una arquitectura de microservicios distribuida. Has evolucionado una simple aplicación monolítica a través de cada etapa crítica del desarrollo profesional: implementaste seguridad robusta, modelaste relaciones de datos complejas, construiste una red de pruebas automatizadas, exploraste la flexibilidad de GraphQL y, finalmente, deconstruiste tu aplicación para hacerla masivamente escalable.

Ya no solo escribes código; diseñas sistemas. Has aprendido en la práctica que la arquitectura de microservicios no es una “bala de plata”, sino una solución poderosa a problemas específicos de crecimiento y escala. Has dominado una de las habilidades más demandadas y respetadas en el desarrollo de software a gran escala, y todo dentro del ecosistema coherente y elegante de NestJS.


10. Recursos Adicionales

Para profundizar en este vasto mundo, la documentación oficial y los patrones de arquitectura son tus mejores aliados.


11. Sugerencias de Siguientes Pasos

Has construido un sistema distribuido básico. Ahora puedes llevarlo a un nivel de producción real explorando estos temas:

  1. Implementar un Broker de Mensajería (RabbitMQ): Reemplaza la comunicación TCP síncrona por un transporter asíncrono como RabbitMQ. Intenta crear un EmailService ficticio que escuche el evento usuario_creado y “envíe” un email de bienvenida, todo en segundo plano.
  2. Configurar un Monorepo (Nx o NestJS Workspaces): Resuelve el problema de compartir código (como DTOs) entre tus servicios. Investiga cómo estructurar tu Gateway y tus microservicios dentro de un solo repositorio (Monorepo) usando las herramientas nativas de NestJS o soluciones más avanzadas como Nx.
  3. Contenerizar y Orquestar (Docker & Kubernetes): El paso final. Aprende a “dockerizar” cada uno de tus microservicios y tu Gateway, y luego usa Kubernetes (K8s) para desplegarlos, gestionarlos y escalarlos automáticamente en un entorno de producción.

12. Invitación a la Acción 💪

¡Felicidades por completar esta serie épica! Has cubierto más terreno que la gran mayoría de los desarrolladores y has construido un portafolio de habilidades increíblemente sólido.

Pero el verdadero aprendizaje comienza ahora. El desafío final es tuyo: extrae un segundo servicio.

Toma el API Gateway que refactorizamos y extrae el TasksService para que sea su propio microservicio. Piensa en los nuevos retos que esto presenta:

  • ¿Cómo se comunica el tasks-service con el auth-service para verificar la propiedad de una tarea? (Pista: Servicio-a-Servicio).
  • ¿Cómo manejas los eventos? ¿Podría el tasks-service emitir un evento tarea_creada que el gateway escuche para una notificación en tiempo real?

La arquitectura es un arte que se domina construyendo. Has completado el tutorial, ahora ve y conviértete en el arquitecto.

¡Feliz escalada!

Deja un comentario

Scroll al inicio

Discover more from Creapolis

Subscribe now to keep reading and get access to the full archive.

Continue reading