Mikroxizmatlar14 min read

Umumiy ko'rinish

An'anaviy (ba'zan monolitik deb ataladigan) ilova arxitekturalaridan tashqari, Nest microservice arxitektura uslubini nativ tarzda qo'llab-quvvatlaydi. Hujjatlarning boshqa joylari

An'anaviy (ba'zan monolitik deb ataladigan) ilova arxitekturalaridan tashqari, Nest microservice arxitektura uslubini nativ tarzda qo'llab-quvvatlaydi. Hujjatlarning boshqa joylarida muhokama qilingan ko'plab tushunchalar, masalan dependency injection, dekoratorlar, exception filterlar, pipelar, guardlar va interceptorlar microservicelarga ham birdek qo'llanadi. Imkon qadar Nest implementatsiya tafsilotlarini abstraksiyalaydi, shuning uchun bir xil komponentlar HTTP asosidagi platformalar, WebSockets va Microservices bo'ylab ishlay oladi. Bu bo'lim microservicelarga xos bo'lgan Nest jihatlarini qamrab oladi.

Nest'da microservice aslida HTTPdan boshqa transport qatlamidan foydalanadigan ilovadir.

Nest bir nechta ichki transport qatlam implementatsiyalarini qo'llab-quvvatlaydi, ular transporters deb ataladi va turli microservice instansiyalari orasida xabarlarni uzatish uchun mas'ul. Ko'pgina transportyorlar nativ tarzda ham request-response, ham event-based xabar uslublarini qo'llab-quvvatlaydi. Nest har bir transportyorning implementatsiya tafsilotlarini request-response va event-based messaging uchun yagona kanonik interfeys ortiga yashiradi. Bu aniq bir transport qatlamining ishonchliligi yoki ishlash xususiyatlaridan foydalanish uchun boshqa transport qatlamiga oson o'tish imkonini beradi va ilova kodingizga ta'sir qilmaydi.

O'rnatish

Microservice qurishni boshlash uchun avval kerakli paketni o'rnating:

Terminal
1$ npm i --save @nestjs/microservices

Boshlash

Microservice instansiyasini yaratish uchun NestFactory klassining createMicroservice() metodidan foydalaning:

TypeScript
main
1import { NestFactory } from '@nestjs/core';
2import { Transport, MicroserviceOptions } from '@nestjs/microservices';
3import { AppModule } from './app.module';
4
5async function bootstrap() {
6  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
7    AppModule,
8    {
9      transport: Transport.TCP,
10    },
11  );
12  await app.listen();
13}
14bootstrap();
Hint

Microservicelar default bo'yicha TCP transport qatlamidan foydalanadi.

createMicroservice() metodining ikkinchi argumenti options obyekti. Bu obyekt ikki a'zodan iborat bo'lishi mumkin:

transportTransportyorni ko'rsatadi (masalan, Transport.NATS)
optionsTransportyorga xos options obyekti, transportyorning xatti-harakatini belgilaydi

options obyekti tanlangan transportyorga xos. TCP transportyori quyida tasvirlangan propertylarni ochadi. Boshqa transportyorlar (masalan, Redis, MQTT va h.k.) uchun mavjud options tavsifi tegishli bobda berilgan.

hostUlanish hostname'i
portUlanish porti
retryAttemptsXabarni qayta urinishlar soni (default: 0)
retryDelayXabarni qayta urinishlar orasidagi kechikish (ms) (default: 0)
serializerChiquvchi xabarlar uchun custom serializer
deserializerKiruvchi xabarlar uchun custom deserializer
socketClassTcpSocketni kengaytiradigan custom Socket (default: JsonSocket)
tlsOptionstls protokolini sozlash options'lari
Hint

Yuqoridagi propertylar TCP transportyoriga xos. Boshqa transportyorlar uchun mavjud options haqida ma'lumot olish uchun tegishli bobga murojaat qiling.

Xabar va hodisa patternlari

Microservicelar xabarlar va hodisalarni patternlar orqali tan oladi. Pattern - bu oddiy qiymat, masalan literal obyekt yoki string. Patternlar avtomatik tarzda serializatsiya qilinadi va xabarning data qismi bilan birga tarmoq orqali yuboriladi. Shu tariqa, xabar yuboruvchilar va iste'molchilar qaysi so'rovlar qaysi handlerlar tomonidan iste'mol qilinishini muvofiqlashtira oladi.

So'rov-javob

Request-response xabar uslubi turli tashqi servislar orasida xabarlarni almashish kerak bo'lganda foydali. Bu paradigma servis xabarni haqiqatan ham qabul qilganini ta'minlaydi (acknowledgment protokolini qo'lda implement qilmasdan). Biroq request-response yondashuvi har doim ham eng mos bo'lmasligi mumkin. Masalan, log asosidagi persistence dan foydalanadigan Kafka yoki NATS streaming kabi streaming transportyorlar boshqa turdagi muammolarga optimallashtirilgan bo'lib, event messaging paradigmasiga ko'proq mos keladi (batafsil event-based messaging ga qarang).

Request-response xabar turini yoqish uchun Nest ikki mantiqiy kanal yaratadi: biri ma'lumot uzatish, ikkinchisi kiruvchi javoblarni kutish uchun. NATS kabi ayrim transportlar uchun bu ikki kanalli qo'llab-quvvatlash qutidan tayyor. Boshqalarida esa Nest alohida kanallarni qo'lda yaratish orqali kompensatsiya qiladi. Bu samarali bo'lsa-da, ba'zi ortiqcha yuk keltirishi mumkin. Shu sababli, agar sizga request-response xabar uslubi kerak bo'lmasa, event-based usuldan foydalanishni ko'rib chiqing.

Request-response paradigmasi asosida message handler yaratish uchun @MessagePattern() dekoratoridan foydalaning, u @nestjs/microservices paketidan import qilinadi. Bu dekoratordan faqat controller klasslarida foydalanish kerak, chunki ular ilovangizning kirish nuqtalari hisoblanadi. Uni providerlarda ishlatish hech qanday ta'sir ko'rsatmaydi, chunki Nest runtime tomonidan e'tiborga olinmaydi.

TypeScript
math.controller
1import { Controller } from '@nestjs/common';
2import { MessagePattern } from '@nestjs/microservices';
3
4@Controller()
5export class MathController {
6  @MessagePattern({ cmd: 'sum' })
7  accumulate(data: number[]): number {
8    return (data || []).reduce((a, b) => a + b);
9  }
10}

Yuqoridagi kodda accumulate() message handler {{ '{' }} cmd: 'sum' {{ '}' }} message patterniga mos keladigan xabarlarni tinglaydi. Message handler bitta argument oladi - mijozdan uzatilgan data. Bu holatda data yig'ish kerak bo'lgan sonlar massividir.

Asinxron javoblar

Message handlerlar sinxron yoki asinxron javob bera oladi, ya'ni async metodlar qo'llab-quvvatlanadi.

TypeScript
1@MessagePattern({ cmd: 'sum' })
2async accumulate(data: number[]): Promise<number> {
3  return (data || []).reduce((a, b) => a + b);
4}

Message handler Observable ham qaytarishi mumkin, bu holda stream tugaguncha natija qiymatlar emit qilinadi.

TypeScript
1@MessagePattern({ cmd: 'sum' })
2accumulate(data: number[]): Observable<number> {
3  return from([1, 2, 3]);
4}

Yuqoridagi misolda message handler uch marta javob beradi, massivdagi har bir element uchun bir martadan.

Eventga asoslangan

Request-response usuli servislar o'rtasida xabar almashish uchun juda qulay, ammo faqat eventlarni publish qilishni xohlaganda va javob kutmasangiz, event-based messaging uchun u unchalik mos emas. Bunday holatlarda request-response uchun ikki kanalni ushlab turish ortiqcha bo'ladi.

Masalan, tizimning bu qismida muayyan shart yuz bergani haqida boshqa servisni xabardor qilmoqchi bo'lsangiz, event-based xabar uslubi ideal bo'ladi.

Event handler yaratish uchun @nestjs/microservices paketidan import qilinadigan @EventPattern() dekoratoridan foydalanishingiz mumkin.

TypeScript
1@EventPattern('user_created')
2async handleUserCreated(data: Record<string, unknown>) {
3  // business logic
4}
Hint

Bitta event pattern uchun bir nechta event handlerlarni ro'yxatdan o'tkazishingiz mumkin va ularning barchasi avtomatik ravishda parallel ishlaydi.

handleUserCreated() event handler 'user_created' eventini tinglaydi. Event handler bitta argument oladi - mijozdan uzatilgan data (bu holatda tarmoq orqali yuborilgan event payload).

Qo'shimcha so'rov tafsilotlari

Murakkabroq ssenariylarda kiruvchi so'rov haqida qo'shimcha tafsilotlarga kirish kerak bo'lishi mumkin. Masalan, wildcard subscriptionlar bilan NATS ishlatayotganda, producent xabarni yuborgan original subjectni olishni xohlashingiz mumkin. Xuddi shuningdek, Kafka bilan xabar sarlavhalariga kirish kerak bo'lishi mumkin. Bunga erishish uchun quyida ko'rsatilgandek built-in dekoratorlardan foydalanishingiz mumkin:

TypeScript
1@MessagePattern('time.us.*')
2getDate(@Payload() data: number[], @Ctx() context: NatsContext) {
3  console.log(`Subject: ${context.getSubject()}`); // e.g. "time.us.east"
4  return new Date().toLocaleTimeString(...);
5}
Hint

@Payload(), @Ctx() va NatsContext@nestjs/microservices paketidan import qilinadi.

Hint

@Payload() dekoratoriga property key ham uzatib, kiruvchi payload obyektidan aniq bir propertyni ajratib olishingiz mumkin, masalan, @Payload('id').

Client (producer klassi)

Client Nest ilovasi Nest microservice bilan xabar almashishi yoki event publish qilishi uchun ClientProxy klassidan foydalanishi mumkin. Bu klass send() (request-response messaging uchun) va emit() (event-driven messaging uchun) kabi bir nechta metodlarni taqdim etadi va masofaviy microservice bilan muloqot qilishga imkon beradi. Bu klass instansiyasini quyidagi yo'llar bilan olishingiz mumkin:

Bitta yondashuv - ClientsModuleni import qilish bo'lib, u register() statik metodini ochadi. Bu metod microservice transportyorlarini ifodalovchi obyektlar massivini qabul qiladi. Har bir obyekt name propertysini o'z ichiga olishi kerak va ixtiyoriy transport propertysi (default Transport.TCP) hamda ixtiyoriy options propertysi bo'lishi mumkin.

name propertysi injection token sifatida ishlaydi va zarur joyda ClientProxy instansiyasini inject qilish uchun foydalaniladi. Bu name qiymati here da ta'riflanganidek, istalgan string yoki JavaScript symbol bo'lishi mumkin.

options propertysi oldin createMicroservice() metodida ko'rganimizdek, xuddi shu propertylarni o'z ichiga olgan obyektdir.

TypeScript
1@Module({
2  imports: [
3    ClientsModule.register([
4      { name: 'MATH_SERVICE', transport: Transport.TCP },
5    ]),
6  ],
7})

Agar sozlash jarayonida konfiguratsiya berish yoki boshqa asinxron jarayonlarni bajarish kerak bo'lsa, registerAsync() metodidan ham foydalanishingiz mumkin.

TypeScript
1@Module({
2  imports: [
3    ClientsModule.registerAsync([
4      {
5        imports: [ConfigModule],
6        name: 'MATH_SERVICE',
7        useFactory: async (configService: ConfigService) => ({
8          transport: Transport.TCP,
9          options: {
10            url: configService.get('URL'),
11          },
12        }),
13        inject: [ConfigService],
14      },
15    ]),
16  ],
17})

Modul import qilingandan so'ng, @Inject() dekoratori yordamida 'MATH_SERVICE' transportyori uchun ko'rsatilgan options bilan sozlangan ClientProxy instansiyasini inject qilishingiz mumkin.

TypeScript
1constructor(
2  @Inject('MATH_SERVICE') private client: ClientProxy,
3) {}
Hint

ClientsModule va ClientProxy klasslari @nestjs/microservices paketidan import qilinadi.

Ba'zan transportyor konfiguratsiyasini client ilovasida hard-code qilish o'rniga boshqa servisdan (masalan, ConfigService) olish kerak bo'lishi mumkin. Bunga erishish uchun ClientProxyFactory klassi yordamida custom provider ro'yxatdan o'tkazishingiz mumkin. Bu klass transportyor options obyektini qabul qilib, moslashtirilgan ClientProxy instansiyasini qaytaradigan statik create() metodini taqdim etadi.

TypeScript
1@Module({
2  providers: [
3    {
4      provide: 'MATH_SERVICE',
5      useFactory: (configService: ConfigService) => {
6        const mathSvcOptions = configService.getMathSvcOptions();
7        return ClientProxyFactory.create(mathSvcOptions);
8      },
9      inject: [ConfigService],
10    }
11  ]
12  ...
13})
Hint

ClientProxyFactory@nestjs/microservices paketidan import qilinadi.

Yana bir variant - @Client() property dekoratoridan foydalanish.

TypeScript
1@Client({ transport: Transport.TCP })
2client: ClientProxy;
Hint

@Client() dekoratori @nestjs/microservices paketidan import qilinadi.

@Client() dekoratoridan foydalanish afzal usul emas, chunki u test qilishni qiyinlashtiradi va client instansiyasini ulashish ham qiyinroq.

ClientProxy lazy. U ulanishni darhol boshlamaydi. Buning o'rniga, birinchi microservice chaqiruvigacha ulanish o'rnatiladi va keyingi chaqiruvlar davomida qayta ishlatiladi. Biroq, agar ulanish o'rnatilmaguncha ilovani bootstrapping qilishni kechiktirmoqchi bo'lsangiz, OnApplicationBootstrap lifecycle hook ichida ClientProxy obyektining connect() metodini chaqirib, ulanishni qo'lda boshlashingiz mumkin.

TypeScript
1async onApplicationBootstrap() {
2  await this.client.connect();
3}

Agar ulanish yaratilmasa, connect() metodi mos xato obyektini qaytarib, reject qiladi.

Xabarlarni yuborish

ClientProxy send() metodini taqdim etadi. Bu metod microservice'ni chaqirish uchun mo'ljallangan va javob bilan Observable qaytaradi. Shunday qilib, emit bo'layotgan qiymatlarga osongina subscribe bo'lamiz.

TypeScript
1accumulate(): Observable<number> {
2  const pattern = { cmd: 'sum' };
3  const payload = [1, 2, 3];
4  return this.client.send<number>(pattern, payload);
5}

send() metodi pattern va payload degan ikki argumentni oladi. pattern @MessagePattern() dekoratorida aniqlanganlardan biriga mos bo'lishi kerak. payload esa masofaviy microservice'ga uzatmoqchi bo'lgan xabarimizdir. Bu metod cold Observable qaytaradi, ya'ni xabar yuborilishi uchun aniq subscribe qilish kerak.

Eventlarni publish qilish

Event yuborish uchun ClientProxy obyektining emit() metodidan foydalaning. Bu metod eventni message brokerga publish qiladi.

TypeScript
1async publish() {
2  this.client.emit<number>('user_created', new UserCreatedEvent());
3}

emit() metodi pattern va payload degan ikki argumentni oladi. pattern @EventPattern() dekoratorida aniqlanganlardan biriga mos bo'lishi kerak, payload esa masofaviy microservice'ga uzatmoqchi bo'lgan event ma'lumotidir. Bu metod hot Observable qaytaradi (send() qaytaradigan cold Observabledan farqli), ya'ni observable'ga subscribe qilsangiz ham, qilmasangiz ham, proxy eventni darhol yetkazishga urinadi.

So'rov scoping

Turli dasturlash tillari fonidan kelganlar uchun Nest'da ko'p narsalar kiruvchi so'rovlar bo'ylab umumiy ekanini bilish ajablanarli bo'lishi mumkin. Bunga ma'lumotlar bazasiga ulanish pooli, global holatli singleton servislar va boshqalar kiradi. E'tibor bering, Node.js request/response multi-threaded stateless modelga amal qilmaydi, bu modelda har bir so'rov alohida thread tomonidan qayta ishlanadi. Natijada singleton instansiyalarni ishlatish ilovalarimiz uchun xavfsiz.

Biroq, ba'zi chekka holatlarda handler uchun requestga bog'liq lifetime kerak bo'lishi mumkin. Bunga GraphQL ilovalarida har-so'rov keshlash, request tracking yoki multi-tenancy kabi ssenariylar kiradi. Scope'larni qanday boshqarish haqida batafsil here da o'qishingiz mumkin.

Request-scoped handlerlar va providerlar @Inject() dekoratori va CONTEXT tokeni kombinatsiyasi orqali RequestContextni inject qilishi mumkin:

TypeScript
1import { Injectable, Scope, Inject } from '@nestjs/common';
2import { CONTEXT, RequestContext } from '@nestjs/microservices';
3
4@Injectable({ scope: Scope.REQUEST })
5export class CatsService {
6  constructor(@Inject(CONTEXT) private ctx: RequestContext) {}
7}

Bu RequestContext obyektiga kirish beradi, unda ikki property mavjud:

TypeScript
1export interface RequestContext<T = any> {
2  pattern: string | Record<string, any>;
3  data: T;
4}

data propertysi message producent yuborgan xabar payloadidir. pattern propertysi esa kiruvchi xabarni qayta ishlaydigan mos handlerni aniqlash uchun ishlatiladigan pattern.

Instansiya holati yangilanishlari

Ulanish va asosiy driver instansiyasi holati haqida real vaqt yangilanishlarini olish uchun status streamiga subscribe bo'lishingiz mumkin. Bu stream tanlangan driverga xos holat yangilanishlarini beradi. Masalan, TCP transportyoridan (default) foydalanayotgan bo'lsangiz, status streami connected va disconnected eventlarini emit qiladi.

TypeScript
1this.client.status.subscribe((status: TcpStatus) => {
2  console.log(status);
3});
Hint

TcpStatus tipi @nestjs/microservices paketidan import qilinadi.

Xuddi shuningdek, serverning status streamiga subscribe bo'lib, server holati haqida xabarlarni olishingiz mumkin.

TypeScript
1const server = app.connectMicroservice<MicroserviceOptions>(...);
2server.status.subscribe((status: TcpStatus) => {
3  console.log(status);
4});

Ichki eventlarni tinglash

Ba'zi holatlarda microservice tomonidan emit qilinadigan ichki eventlarni tinglashni xohlashingiz mumkin. Masalan, xato yuz berganda qo'shimcha operatsiyalarni ishga tushirish uchun error eventini tinglashingiz mumkin. Buning uchun quyida ko'rsatilgandek on() metodidan foydalaning:

TypeScript
1this.client.on('error', (err) => {
2  console.error(err);
3});

Xuddi shuningdek, serverning ichki eventlarini tinglashingiz mumkin:

TypeScript
1server.on<TcpEvents>('error', (err) => {
2  console.error(err);
3});
Hint

TcpEvents tipi @nestjs/microservices paketidan import qilinadi.

Asosiy driverga kirish

Murakkabroq use-case'larda asosiy driver instansiyasiga kirish kerak bo'lishi mumkin. Bu ulanishni qo'lda yopish yoki driverga xos metodlardan foydalanish kabi ssenariylar uchun foydali. Biroq, ko'p hollarda driverga to'g'ridan-to'g'ri kirish kerak emasligini yodda tuting.

Buning uchun unwrap() metodidan foydalanishingiz mumkin, u asosiy driver instansiyasini qaytaradi. Generic type parametri kutilayotgan driver instansiyasi turini ko'rsatishi kerak.

TypeScript
1const netServer = this.client.unwrap<Server>();

Bu yerda Server net modulidan import qilinadigan tip.

Xuddi shuningdek, serverning asosiy driver instansiyasiga kirishingiz mumkin:

TypeScript
1const netServer = server.unwrap<Server>();

Timeoutlarni boshqarish

Distribyutlangan tizimlarda microservice'lar ba'zan ishlamay qolishi yoki mavjud bo'lmasligi mumkin. Cheksiz kutishni oldini olish uchun timeoutlardan foydalanishingiz mumkin. Timeout boshqa servislarga murojaat qilishda juda foydali pattern hisoblanadi. Microservice chaqiruvlariga timeout qo'llash uchun RxJS timeout operatoridan foydalanishingiz mumkin. Agar microservice belgilangan vaqt ichida javob bermasa, xato chiqariladi va uni mos ravishda ushlab, qayta ishlashingiz mumkin.

Buni amalga oshirish uchun rxjs paketidan foydalaning. Shunchaki pipe ichida timeout operatorini ishlating:

TypeScript
1this.client
2  .send<TResult, TInput>(pattern, data)
3  .pipe(timeout(5000));
Hint

timeout operatori rxjs/operators paketidan import qilinadi.

5 soniyadan so'ng, microservice javob bermasa, xato chiqaradi.

TLS qo'llab-quvvatlash

Shaxsiy tarmoqdan tashqarida muloqot qilganda xavfsizlik uchun trafikni shifrlash muhim. NestJS'da bu Node'ning built-in TLS moduli yordamida TCP ustida TLS bilan amalga oshiriladi. Nest TCP transportida TLSni built-in qo'llab-quvvatlaydi va microservice'lar yoki clientlar orasidagi muloqotni shifrlash imkonini beradi.

TCP serveri uchun TLSni yoqish uchun PEM formatidagi private key va sertifikat kerak bo'ladi. Bular server options'iga tlsOptionsni o'rnatib, key va cert fayllarini ko'rsatish orqali qo'shiladi:

TypeScript
1import * as fs from 'fs';
2import { NestFactory } from '@nestjs/core';
3import { AppModule } from './app.module';
4import { MicroserviceOptions, Transport } from '@nestjs/microservices';
5
6async function bootstrap() {
7  const key = fs.readFileSync('<pathToKeyFile>', 'utf8').toString();
8  const cert = fs.readFileSync('<pathToCertFile>', 'utf8').toString();
9
10  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
11    AppModule,
12    {
13      transport: Transport.TCP,
14      options: {
15        tlsOptions: {
16          key,
17          cert,
18        },
19      },
20    },
21  );
22
23  await app.listen();
24}
25bootstrap();

Client TLS orqali xavfsiz muloqot qilishi uchun biz tlsOptions obyektini bu safar CA sertifikati bilan belgilaymiz. Bu server sertifikatini imzolagan authority sertifikatidir. Bu client server sertifikatiga ishonishini va xavfsiz ulanishni o'rnata olishini ta'minlaydi.

TypeScript
1import { Module } from '@nestjs/common';
2import { ClientsModule, Transport } from '@nestjs/microservices';
3
4@Module({
5  imports: [
6    ClientsModule.register([
7      {
8        name: 'MATH_SERVICE',
9        transport: Transport.TCP,
10        options: {
11          tlsOptions: {
12            ca: [fs.readFileSync('<pathToCaFile>', 'utf-8').toString()],
13          },
14        },
15      },
16    ]),
17  ],
18})
19export class AppModule {}

Agar sozlashingiz bir nechta ishonchli authority'larni o'z ichiga olsa, CA'lar massivini ham uzatishingiz mumkin.

Hammasi sozlangach, servislaringizda clientdan foydalanish uchun @Inject() dekoratori orqali ClientProxyni odatdagidek inject qilishingiz mumkin. Bu Node'ning TLS moduli shifrlash tafsilotlarini boshqaradigan NestJS microservice'lari bo'ylab shifrlangan muloqotni ta'minlaydi.

Batafsil ma'lumot uchun Node'ning TLS documentation hujjatiga murojaat qiling.

Dinamik konfiguratsiya

Microservice'ni ConfigService ( @nestjs/config paketidan) orqali sozlash kerak bo'lganda, lekin injection context faqat microservice instansiyasi yaratilgandan keyin mavjud bo'lsa, AsyncMicroserviceOptions yechim beradi. Bu yondashuv dinamik konfiguratsiyaga imkon beradi va ConfigService bilan silliq integratsiyani ta'minlaydi.

TypeScript
1import { ConfigService } from '@nestjs/config';
2import { AsyncMicroserviceOptions, Transport } from '@nestjs/microservices';
3import { AppModule } from './app.module';
4
5async function bootstrap() {
6  const app = await NestFactory.createMicroservice<AsyncMicroserviceOptions>(
7    AppModule,
8    {
9      useFactory: (configService: ConfigService) => ({
10        transport: Transport.TCP,
11        options: {
12          host: configService.get<string>('HOST'),
13          port: configService.get<number>('PORT'),
14        },
15      }),
16      inject: [ConfigService],
17    },
18  );
19
20  await app.listen();
21}
22bootstrap();