paint-brush
Cómo crear microservicios en Nest.JSpor@amatusenco
14,306 lecturas
14,306 lecturas

Cómo crear microservicios en Nest.JS

por Artiom Matusenco2021/09/24
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

NestJS es un marco inspirado en Angular que proporciona una estructura de proyecto fuertemente determinada, patrones de diseño comunes y mejores enfoques. Es altamente extensible debido a su estructura y mecanismo IoC que permite conectar cualquier biblioteca de una manera muy conveniente. NestJS brinda compatibilidad con las capas de transporte más populares para la comunicación entre servicios: Redis pub/sub, NATS, MQTT, RabbitMQ, Kafka, gRPC y otras.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Cómo crear microservicios en Nest.JS
Artiom Matusenco HackerNoon profile picture

Cada vez que empiezo un nuevo proyecto me pregunto: "¿qué tipo de marco debo elegir?".


Por supuesto, depende de múltiples factores, incluida la experiencia del equipo, qué problema se debe resolver, qué escala se espera , etc.


Ok, lo más probable es que sea una arquitectura basada en microservicios. Tal enfoque podría basarse en casi todos los casos a partir de los puntos anteriores. Pero espere, ¿en qué se diferencian los microservicios del monolito desde el punto de vista de la programación? Después de pensar un poco, puedo decir que son casi idénticos pero con una sola excepción: los microservicios necesitan comunicarse entre sí de alguna manera.


Aparte de eso, todas las mejores prácticas y las características requeridas del marco son las mismas:


  • arquitectura de proyecto estructurado
  • conjunto de herramientas y abstracciones
  • alto rendimiento
  • extensibilidad
  • conexión conveniente a bases de datos
  • gestión de dependencias entre bloques lógicos
  • posibilidad de dividir bloques lógicos
  • prueba fácil y conveniente
  • amigable para desarrolladores
  • documentación clara y amplia comunidad, etc.


Hay muchos marcos basados en NodeJS, pero me gustaría compartir mi experiencia en la creación de microservicios con NestJS.


Todos los puntos enumerados anteriormente están cubiertos por el marco NestJS:

  • proporciona compatibilidad con las capas de transporte más populares para la comunicación entre servicios: Redis pub/sub, NATS, MQTT, RabbitMQ, Kafka, gRPC y otras.
  • Es un marco inspirado en Angular que proporciona una estructura de proyecto fuertemente determinada, patrones de diseño comunes y mejores enfoques.
  • Proporciona varias herramientas que incluyen, entre otras, cli, generador de proyectos, decoradores, interfaces, caché, registro, autorización, etc.
  • es altamente extensible debido a su estructura y mecanismo IoC que permite conectar cualquier biblioteca de una manera muy conveniente.
  • Proporciona controladores para las bases de datos más populares y la posibilidad de escribir su propio controlador para una base de datos especial.
  • gestiona las dependencias y permite mantener módulos independientes entre sí.
  • independientemente de algún tipo de utilidad o simplemente código repetitivo.
  • proporciona herramientas y técnicas de prueba que permiten escribir pruebas unitarias eficientes.
  • tiene una comunidad amplia y una documentación muy clara y detallada.


Juguemos un poco y creemos dos microservicios con interacción entre sí y un BFF que traducirá los protocolos de comunicación entre servicios en protocolos compatibles con la web.


Como ejemplo sencillo propondría los siguientes casos:

Casos de ejemplo Este es un ejemplo muy simple que representa la separación de responsabilidades y la segregación de datos. Aquí tenemos un concepto de account que es una combinación de un user y un profile . Imaginemos que tenemos un servicio de users genéricos que se encarga únicamente de la autenticación. Por razones de seguridad, no debemos almacenar credenciales y datos personales en la misma base de datos ni administrarlos en el mismo servicio. Por eso disponemos de un servicio de profiles dedicado. Es solo una historia, pero nuestro cliente imaginario de alguna manera debería obtener una account que contenga datos tanto del user como del profile . Es por eso que necesitamos un servicio BFF (backend para frontend) que combine datos de diferentes microservicios y regrese al cliente.


Primero instalemos nest-cli y generemos proyectos nest:

 $ mkdir nestjs-microservices && cd nestjs-microservices $ npm install -g @nestjs/cli $ nest new users $ nest new profiles $ nest new bff


De forma predeterminada, NestJS genera un servidor Http, así que actualicemos los users y los profiles para que se comuniquen a través de un protocolo basado en eventos, por ejemplo: Redis Pub/Sub:

 $ npm i --save @nestjs/microservices


/src/main.ts

 const app = await NestFactory.createMicroservice<MicroserviceOptions>( AppModule, { transport: Transport.REDIS, options: { url: 'redis://localhost:6379', }, }, ); app.listen(() => console.log('Services started'));


Un servicio de NestJS consta de módulos y los módulos se dividen en diferentes partes, pero las más importantes son los controladores y los servicios. Los controladores son responsables de proporcionar una API de servicio externo. Vamos a definirlos para servicios de users y profiles .

/users/src/app.controller.ts

 @Controller() export class AppController { @MessagePattern({ cmd: 'get_users' }) getUsers() { return this.appService.getUsers(); } }


/users/src/app.service.ts

 @Injectable() export class AppService { users = [ { id: '1', login: 'bob' }, { id: '2', login: 'john' }, ]; getUsers(): User[] { return this.users; } }


/profiles/src/app.controller.ts

 @Controller() export class AppController { @MessagePattern({ cmd: 'get_profiles' }) getProfiles() { return this.appService.getProfiles(); } }


/profiles/src/app.service.ts

 @Injectable() export class AppService { profiles = [ { id: '1', name: 'Bob' }, { id: '2', name: 'John' }, ]; getProfiles(): Profile[] { return this.profiles; } }


En aras de la simplicidad, no he usado bases de datos y solo he definido colecciones en memoria.


Una vez que los servicios están listos, tenemos que crear el servicio BFF que expone la interfaz REST compatible con la web:

/bff/src/app.controller.ts

 @Controller() export class AppController { constructor( @Inject('PUBSUB') private readonly client: ClientProxy, ) {} @Get('accounts') async getAccounts(): Promise<Account[]> { const users = await this.client .send<User[]>({ cmd: 'get_users' }, { page: 1, items: 10 }) .toPromise(); const profiles = await this.client .send<Profile[]>({ cmd: 'get_profiles' }, { ids: users.map((u) => u.id) }) .toPromise(); return users.map<Account>((u) => ({ ...u, ...profiles.find((p) => p.id === u.id), })); } }


Como puede ver, hay una dependencia inyectada con el token 'PUBSUB'. Definámoslo también:

/bff/src/app.module.ts

 @Module({ imports: [ ClientsModule.register([ { name: 'PUBSUB', transport: Transport.REDIS, options: { url: 'redis://localhost:6379', }, }, ]), ], controllers: [AppController], providers: [AppService, Pubsub], }) export class AppModule {}


En este punto, se implementa el caso #1. ¡Vamos a ver!


Ejecute el siguiente comando contra cada proyecto:

 npm run start:dev


Una vez que se inicien los tres servicios, enviemos una solicitud para obtener cuentas:

 curl http://localhost:3000/accounts | jq [ { "id": 1, "login": "bob", "name": "Bob" }, { "id": 2, "login": "john", "name": "John" } ]


He usado las siguientes dos herramientas: curl y jq . Siempre puede usar sus herramientas preferidas o simplemente seguir el artículo e instalarlas usando cualquier administrador de paquetes con el que se sienta cómodo.


Llamaría su atención que hay dos estilos de mensajes diferentes: asíncrono y sincrónico. Como puede ver en los casos de ejemplo, las imágenes n.° 1 y n.° 2 se definen utilizando el estilo de mensaje de solicitud-respuesta, por lo tanto, síncrono. Se elige porque tenemos que devolver datos solicitados por el cliente. Pero en los casos #3 y #4, puede ver un solo comando de dirección para guardar datos. Esta vez es un estilo de mensaje asincrónico basado en eventos. NestJS proporciona los siguientes decoradores para cubrir ambos casos:

  • @MessagePattern() - para el estilo de mensajes sincrónicos
  • @EventPattern() - para el estilo de mensajes asincrónicos


Los casos n.° 2 y n.° 4 son bastante similares a los casos n.° 1 y n.° 3, por lo que omitiré su implementación en este ejemplo.


Implementemos el caso #3. Esta vez usaremos el método @EventPattern() emit() el decorador @EventPattern().


/bff/src/app.controller.ts

 @Post('accounts') async createAccount(@Body() account: Account): Promise<void> { await this.client.emit({ cmd: 'create_account' }, account); }


/profiles/src/app.controller.ts

 @EventPattern({ cmd: 'create_account' }) createProfile(profile: Profile): Profile { return this.appService.createProfile(profile); }


/profiles/src/app.service.ts

 createProfile(profile: Profile): Profile { this.profiles.push(profile); return profile; }


/users/src/app.controller.ts

 @EventPattern({ cmd: 'create_account' }) createUser(account: Account): User { const {id, login} = account; return this.appService.createUser({id, login}); }


/users/src/app.service.ts

 createProfile(user: User): User { this.users.push(user); return user; }


agreguemos una cuenta mira el resultado:

 curl -X POST http:/localhost:3000/accounts \ -H "accept: application/json" -H "Content-Type: application/json" \ --data "{'id': '3', 'login': 'jack', 'name': 'Jack'}" -i HTTP/1.1 201 Created


 curl http://localhost:3000/accounts | jq [ { "id": "1", "login": "bob", "name": "Bob" }, { "id": "2", "login": "john", "name": "John" }, { "id": "3", "login": "jack", "name": "Jack" } ]


Consulte el código completo de los servicios descritos anteriormente: https://github.com/artiom-matusenco/amazing-microservices-with-nestjs .


¡Eso es todo! 🙃


Pero espere... ¿qué pasa con el monitoreo, la autorización, el rastreo, la tolerancia a fallas, las métricas...?

Diría que es una buena pregunta y tengo una buena respuesta: en el mejor de los casos, todas estas cosas geniales deben descargarse a nivel de infraestructura; los microservicios solo deberían preocuparse por la lógica empresarial y, probablemente, por algunas cosas específicas, como el seguimiento analítico.

Conclusión

NestJS ofrece la posibilidad de crear microservicios ligeros, bien estructurados y sorprendentes. Las herramientas y características listas para usar hacen que el desarrollo, la extensión y el mantenimiento sean agradables y eficientes.

Referencias