IlmHamroh
JavaScript Full-stack/30-qism. NestJS45/65-dars18 daqiqa
Mundarija (28)

Mikroservis asoslari NestJS'da: @MessagePattern, @EventPattern va ClientProxy

Qisqacha: Nest ilovasi HTTP'siz ham ishlay oladi — mikroservis sifatida: u xabarlarni transport (TCP, Redis, NATS, RabbitMQ, Kafka, gRPC) orqali qabul qiladi. Ikki xil xabar bor: @MessagePattern — so'rov-javob (client.send() javob kutadi), @EventPattern — hodisa (client.emit() javob kutmaydi). Boshqa xizmat ClientProxy bilan gaplashadi. Controller, DI, pipe, guard — hammasi HTTP'dagidek. Lekin avval savol: sizga mikroservis kerakmi? Ko'pincha javob — yo'q.

Bu darsda

  • Monolit va mikroservis farqini va mikroservisning haqiqiy narxini aytib bera olasiz.
  • createMicroservice bilan TCP xizmat yozib, @MessagePattern va @EventPattern handler'larini qo'shasiz.
  • ClientProxy ning send (sovuq) va emit (darhol) farqini haqiqiy chiqishda ko'rasiz.
  • RpcException, noma'lum pattern va timeout xatolarini o'qiysiz.
  • HTTP gateway va hybrid ilova yasaysiz.

Oldin bilishingiz kerak: Boshqa xabar broker'lari, Global enhancer'lar va bajarilish tartibi, Interceptor'lar va RxJS, GraphQL chuqur NestJS'da.

Oldingi ikki darsda GraphQL'ni ko'rdik. Endi Nest ekotizimining yana bir katta qismi — mikroservislar. Bu darsda asoslar va eng sodda transport — TCP. Keyingi darsda broker'li transportlar va ishonchlilik. Kanon tajribasida ham TCP sinalgan; brokerlar Docker talab qiladi va bizda sinalmagan.

1. Nega bu kerak?

1.1 Monolit va mikroservis

vazifalar-api — monolit (monolith): bitta jarayon, bitta kod bazasi, bitta deploy. Vazifalar, eksport, salomatlik — hammasi bir joyda. Modullar bir-birini oddiy metod chaqiruvi bilan chaqiradi: nanosekundlar, xatosiz.

Mikroservis (microservice) arxitekturasida tizim alohida dasturlarga bo'linadi: "vazifalar xizmati", "hisobot xizmati", "bildirishnoma xizmati". Har biri o'z jarayonida, ko'pincha o'z bazasi bilan, alohida deploy qilinadi. Ular bir-biriga tarmoq orqali xabar yuboradi. Bu va keyingi darslarda "xizmat" so'zi shunday alohida dasturni ham bildiradi — kontekstdan ajrating: VazifalarService — klass, "hisobot xizmati" — alohida jarayon.

O'xshatish: oilaviy oshxona va restoranlar majmuasi. Oilaviy oshxonada bitta oshpaz hamma narsani qiladi: tez, oddiy, gaplashish shart emas. Katta restoranlar majmuasida esa har oshxona alohida — novvoyxona, kabobxona, shirinliklar. Har biri mustaqil ishlaydi va yopiladi, lekin buyurtmalar kuryer bilan yuboriladi. Kuryer kechikishi, adashishi, yo'lda yo'qolishi mumkin.

1.2 Narxi

Mikroservisning foydasi — mustaqillik: har jamoa o'z xizmatini o'zi deploy qiladi, bitta xizmat yiqilsa boshqasi ishlaydi, yuk tushgan xizmatni alohida kengaytirish mumkin. Narxi esa katta:

Monolit Mikroservislar
Modullararo chaqiruv metod: nanosekund tarmoq: millisekund, yiqilishi mumkin
Tranzaksiya bitta baza tranzaksiyasi taqsimlangan — outbox, saga
Deploy bitta har xizmat alohida, versiyalar mosligi
Xato qidirish bitta log ko'p xizmat bo'ylab kuzatuv (tracing)

Ko'p jamoalar boshida mikroservisga o'tib, keyin afsuslanadi. Amaliy qoida: avval yaxshi bo'lingan monolit, mikroservis — aniq sabab bo'lganda (alohida jamoa, alohida yuk, alohida til). Bu mavzuni Modular monolit va hexagonal arxitektura NestJS'da va arxitektura qismidagi monolit, modular monolit, mikroservis darsida davom ettiramiz.

Bu darsda esa texnikani o'rganamiz: tanlov qilinganda Nest'da qanday yoziladi.

2. Birinchi mikroservis

2.1 Server tomoni

bash
npm i --save-exact @nestjs/microservices@12.2.0

Biz demo loyihada (NestJS 12.2.0) "hisobot xizmati" yozdik:

ts
import { Controller, Module } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import {
  EventPattern, MessagePattern, Payload, Transport,
} from "@nestjs/microservices";
import type { MicroserviceOptions } from "@nestjs/microservices";

@Controller()
class HisobotController {
  @MessagePattern({ cmd: "statistika" })
  statistika(@Payload() d: { egasi: number }) {
    return { egasi: d.egasi, jami: 12, bajarilgan: 5 };
  }

  @EventPattern("vazifa.bajarildi")
  bajarildi(@Payload() d: { id: number }) {
    jurnal.push(`hodisa: vazifa.bajarildi ${d.id}`);
  }
}
@Module({ controllers: [HisobotController] })
class HisobotModule {}

const mikro = await NestFactory
  .createMicroservice<MicroserviceOptions>(HisobotModule, {
    transport: Transport.TCP,
    options: { host: "127.0.0.1", port: 4591 },
  });
await mikro.listen();

HTTP ilovadan farqlar:

  • NestFactory.createMicroservice — create o'rnida. Ilova HTTP port ochmaydi, transport serverini ochadi (bu yerda TCP 4591).
  • Controller'da @Get/@Post o'rnida @MessagePattern va @EventPattern. Controller — o'sha @Controller() klassi.
  • @Payload() — xabar tanasi (HTTP'dagi @Body() ning o'rnidosh).

Request lifecycle darsidagi zanjir mikroservisda ham deyarli shunday. Bitta farq bor — middleware HTTP'ga xos, transport xabariga u ishlamaydi:

Qadam HTTP so'rovda Mikroservis xabarida
middleware ishlaydi yo'q
guard ishlaydi ishlaydi (switchToRpc())
interceptor (oldin) ishlaydi ishlaydi
pipe @Body(), @Param() @Payload()
handler @Get, @Post @MessagePattern, @EventPattern
interceptor (keyin) ishlaydi ishlaydi
xato filtri HttpException RpcException («Xatolar» bo'limi)

Guard ichida HTTP so'rovi o'rniga xabar ma'lumoti olinadi: kontekst.switchToRpc().getData() — Guard'lar darsidagi switchToHttp() ning transport varianti.

Pattern (naqsh) — xabarning "manzili". HTTP'da manzil — metod va yo'l (GET /statistika), mikroservisda — pattern: obyekt ({ cmd: "statistika" }) yoki satr ("vazifa.bajarildi").

2.2 Ikki xil xabar

@MessagePattern @EventPattern
Ma'nosi "bajar va javob ber" "shunday bo'ldi"
Mijoz metodi client.send() client.emit()
Javob bor yo'q
Handler topilmasa mijoz xato oladi server logida xato

Birinchisi — so'rov-javob (request-response): "statistikani hisobla". Ikkinchisi — hodisa (event-based): "vazifa bajarildi — kimga kerak bo'lsa, o'zi biladi" — Hodisalar darsidagi domen hodisasi, faqat endi jarayonlar orasida.

3. ClientProxy — mijoz tomoni

3.1 HTTP gateway

Odatda tashqi dunyo (brauzer, mobil ilova) mikroservis bilan to'g'ridan gaplashmaydi. Oldida HTTP API turadi — API gateway: u HTTP so'rovni qabul qiladi va kerakli xizmatga xabar yuboradi.

ts
import { ClientsModule, Transport } from "@nestjs/microservices";
import type { ClientProxy } from "@nestjs/microservices";

@Controller()
class GatewayController {
  constructor(
    @Inject("HISOBOT") private readonly hisobot: ClientProxy,
  ) {}

  @Get("statistika")
  stat() {
    return this.hisobot.send({ cmd: "statistika" }, { egasi: 7 });
  }
}

@Module({
  imports: [
    ClientsModule.register([{
      name: "HISOBOT", transport: Transport.TCP,
      options: { host: "127.0.0.1", port: 4591 },
    }]),
  ],
  controllers: [GatewayController],
})
class GatewayModule {}

ClientsModule.register — dinamik modul: HISOBOT tokeni ostida ClientProxy provider'ini yaratadi. Sozlama config'dan kelishi kerak bo'lsa — registerAsync (useFactory + inject: [ConfigService]). send Observable qaytaradi. Controller uni to'g'ridan qaytardi — Nest HTTP Observable'ni kutib, natijani javob qiladi (Interceptor'lar va RxJS).

Ikki ilovani ishga tushirib, HTTP so'rov yubordik (biz ishga tushirdik):

text
1) HTTP → gateway → TCP
   GET /statistika → 200 {"egasi":7,"jami":12,"bajarilgan":5}

Zanjir: fetch → gateway (HTTP) → send → TCP → hisobot xizmati → javob → TCP → gateway → HTTP javob.

flowchart LR
  B["Brauzer"] -->|"HTTP GET"| G["API gateway (Nest HTTP)"]
  G -->|"TCP send cmd statistika"| H["Hisobot xizmati"]
  H -->|"javob"| G
  V["Vazifalar xizmati"] -.->|"TCP emit vazifa.bajarildi"| H

Diagrammada uzluksiz strelkalar — so'rov-javob (javob qaytadi), uzuq strelka — hodisa (javob yo'q).

3.2 send — sovuq Observable

Muhim nozik joy. send qaytargan Observable sovuq (cold): obuna bo'lmaguncha hech narsa yuborilmaydi. Biz xizmatdagi handler'ga jurnal qo'ydik va tekshirdik:

ts
const kuzatiladi = mijoz.send({ cmd: "statistika" }, { egasi: 1 });
await kut(100);
console.log("subscribe oldidan jurnal:", JSON.stringify(jurnal));
const natija = await firstValueFrom(kuzatiladi);
console.log("natija:", JSON.stringify(natija));
console.log("subscribe dan keyin:", JSON.stringify(jurnal));
text
2) send — "sovuq" Observable
   subscribe oldidan jurnal: []
   natija: {"egasi":1,"jami":12,"bajarilgan":5}
   subscribe dan keyin: ["statistika(1)"]

100 ms kutdik — xizmat hech narsa olmadi. firstValueFrom obuna bo'lgandagina xabar ketdi. Tuzoq: this.hisobot.send(...) ni yozib, natijani qaytarmasangiz yoki await firstValueFrom qilmasangiz — xabar umuman yuborilmaydi. Xato ham, ogohlantirish ham yo'q.

3.3 emit — darhol ketadi

text
3) emit — subscribe siz ham ketadi
   jurnal: ["hodisa: vazifa.bajarildi 7"]
ERROR [Server] There is no matching event handler defined in the remote service. Event pattern: vazifa.ochirildi

emit obunasiz ham yubordi — Nest uni darhol ulaydi (hot). Ikkinchi emit — vazifa.ochirildi, unga handler yo'q. Mijoz hech narsa sezmadi. Xatoni xizmat o'z logiga yozdi: "masofaviy xizmatda mos hodisa handler'i yo'q. Hodisa pattern'i: vazifa.ochirildi". Hodisa yo'qoldi. Bu "fire-and-forget" ning tabiati: kafolat kerak bo'lsa, broker va tasdiqlash (ack) kerak — keyingi dars.

Tekshirib ko'ring: Controller metodida this.hisobot.send({ cmd: "hisobla" }, d); deb yozildi, qaytarilmadi. Metod { ok: true } qaytaradi. Hisobot xizmati hisoblaydimi?

Javob

Yo'q. send sovuq Observable qaytaradi va unga hech kim obuna bo'lmadi — xabar tarmoqqa chiqmadi. Agar javob kerak bo'lmasa, emit ishlating (u darhol ketadi). Agar javob kerak bo'lsa, return this.hisobot.send(...) yoki await firstValueFrom(this.hisobot.send(...)).

4. Pattern qanday solishtiriladi

Obyekt pattern'da kalitlar tartibi muhimmi? Xizmatdagi handler { cmd: "ikki", v: 1 } edi, biz { v: 1, cmd: "ikki" } yubordik. Yana @Ctx() bilan xizmat pattern'ni qanday ko'rishini so'radik:

ts
@MessagePattern({ cmd: "kim" })
kim(@Ctx() ctx: TcpContext) {
  return ctx.getPattern();
}
text
4) kalitlar tartibi
   ikki kalitli pattern
   "{\"cmd\":\"kim\"}"

Tartib muhim emas. Nest pattern obyektini satrga aylantiradi va kalitlarni alifbo bo'yicha saralaydi (@nestjs/microservices manbasidagi transformPatternToRoute). Ichkarida pattern — {"cmd":"kim"} satri, handler'lar shu satr bo'yicha Map da turadi. @Ctx() — transport konteksti: TCP'da TcpContext (pattern, soket), Kafka'da KafkaContext (partition, offset) va hokazo transportga xos ma'lumot.

5. Xatolar

5.1 Uch xil xato

ts
@MessagePattern({ cmd: "topilmadi" })
topilmadi() {
  throw new RpcException({
    kod: "vazifa-topilmadi", xabar: "Vazifa 99 topilmadi",
  });
}

@MessagePattern({ cmd: "buzuq" })
buzuq() {
  throw new Error("ombor uzildi");
}

Mijoz uchala pattern'ni (topilmadi, buzuq va umuman yo'q yoq) chaqirdi. Chiqishda → dan keyingi qism — mijoz nima olgani, ERROR bilan boshlangan qator — xizmatning o'z logi:

text
5) xatolar
   topilmadi → {"kod":"vazifa-topilmadi","xabar":"Vazifa 99 topilmadi"}
ERROR [RpcExceptionsHandler] Error: ombor uzildi
   buzuq → {"status":"error","message":"Internal server error"}
   yoq → "There is no matching message handler defined in the remote service."
  • RpcException — mikroservisning HttpException i. Unga berilgan obyekt mijozga aynan yetib keldi. Mijoz xatoning turini shu obyektdan biladi.
  • Oddiy Error — xizmat uni logga yozdi (RpcExceptionsHandler), mijoz esa faqat {"status":"error","message":"Internal server error"} oldi. Ichki tafsilot sizmadi — HTTP'dagi xavfsiz xulq bilan bir xil.
  • Noma'lum pattern — mijoz satr oldi: "masofaviy xizmatda mos xabar handler'i aniqlanmagan". Kanon tajribasida ham aynan shu matn.

HTTP'dagidek global xato filtri ham yozish mumkin — @Catch(RpcException) va BaseRpcExceptionFilter.

5.2 Timeout

ts
await firstValueFrom(
  mijoz.send({ cmd: "sekin" }, {}).pipe(timeout(100)),
);
text
   sekin + timeout(100) → TimeoutError

Xizmat 500 ms uxladi, mijoz 100 ms dan keyin rxjs TimeoutError oldi. Har send ga timeout qo'ying. Usiz xizmat osilsa, gateway'ning HTTP so'rovi ham osiladi, keyin hovuz to'ladi va zanjir bo'ylab hamma narsa to'xtaydi. Mikroservis tizimlarida bunday "zanjirli yiqilish" (cascading failure) eng ko'p uchraydigan halokat.

Timeout so'rovni bekor qilmaydi: xizmat baribir 500 ms ishlab, javob yuboradi — mijoz uni tashlab yuboradi. Xizmat tomonda og'ir ish bo'lsa, buni hisobga oling.

6. Tezlik

Kanon tajribasi TCP'da 1 000 ta ketma-ket so'rov-javobni o'lchagan. Biz ham qayta o'lchadik va parallel variantni qo'shdik:

text
6) 1000 ketma-ket so'rov-javob
   o'rtacha 0.100 ms
   1000 parallel: 48 ms

Nima bo'ldi? Ketma-ket yuborganda har so'rov oldingisining javobini kutdi — o'rtacha 0.1 ms. Parallel yuborganda 1 000 ta so'rov bitta ulanishda bir vaqtda "havoda" turdi va hammasi 48 ms da qaytdi. Bu qanday ishlashini 3-mashqda modellaymiz.

Kanon: birinchi so'rov 5.4 ms (ulanish), keyingilari o'rtacha 0.118 ms. Bizda 0.100 ms. Bitta mashina, ikkalasi ham 127.0.0.1 — haqiqiy tarmoqsiz. Haqiqiy tarmoqda (ikki server, bitta ma'lumotlar markazi) bunga yana 0.2–1 ms qo'shiladi.

Bu raqamni metod chaqiruvi bilan solishtiring: oddiy JS funksiya chaqiruvi nanosekundlarda o'lchanadi — kamida mingdan bir necha baravar tezroq. Monolit ichidagi VazifalarService.ol() ni mikroservisga ko'chirsangiz, har chaqiruv shuncha qimmatlashadi va yangi xato turlari paydo bo'ladi (timeout, ulanish uzilishi). Bu «Narxi» bo'limidagi jadvalning raqamdagi ifodasi.

7. Hybrid ilova

Bitta Nest ilovasi bir vaqtda ham HTTP, ham mikroservis bo'la oladi — hybrid ilova:

ts
const app = await NestFactory.create(HisobotModule);
app.connectMicroservice<MicroserviceOptions>({
  transport: Transport.TCP,
  options: { host: "127.0.0.1", port: 4592 },
});
await app.startAllMicroservices();
await app.listen(0, "127.0.0.1");

HisobotController ga @Get("holat") ham qo'shdik — bitta controller'da HTTP marshrut va pattern handler'lari:

text
7) hybrid ilova
   TCP: {"egasi":3,"jami":12,"bajarilgan":5}
   HTTP GET /holat → 200 {"ok":true}

Ikki transport — bitta DI konteyner, bitta xizmatlar to'plami. Hybrid ilova asta-sekin ko'chish uchun qulay. Monolit HTTP'da ishlashda davom etadi va yangi xizmatlar bilan TCP yoki broker orqali gaplasha boshlaydi. connectMicroservice ni bir necha marta chaqirib, bir nechta transport ulash mumkin. Rasmiy hujjatga ko'ra (Hybrid application sahifasi, 2026-11-02 holatiga), ulangan mikroservis global pipe, guard, interceptor va xato filtrlarini sukut bo'yicha olmaydi — useGlobal*() bilan ulanganini ham, APP_PIPE, APP_GUARD, APP_INTERCEPTOR, APP_FILTER provider'lari bilan ulanganini ham. Meros olish uchun connectMicroservice(…, { inheritAppConfig: true }) kerak, useGlobal*() chaqiruvlari esa connectMicroservice dan oldin turishi lozim (biz buni sinamadik).

8. JS modelida pattern va javob

Nest mikroservisining ichidagi uch g'oyani paketsiz modelda ko'raylik: pattern'ni satrga aylantirish (kalitlar saralangan), so'rov-javobda id, hodisada id yo'q. "Tarmoq"ni JSON.parse(JSON.stringify(...)) bilan modellaymiz — har xabar seriyalanadi. Blok brauzerda ishlaydi:

js
// Nest mikroservis g'oyasi: pattern → handler, so'rov-javob va hodisa
const kalit = (p) => typeof p === "string" ? p : JSON.stringify(
  Object.fromEntries(Object.entries(p).toSorted(
    ([a], [b]) => a.localeCompare(b),
  )),
);
const tarmoq = (x) => JSON.parse(JSON.stringify(x)); // seriyalash

class Server {
  #xabarlar = new Map();
  #hodisalar = new Map();
  messagePattern(p, fn) { this.#xabarlar.set(kalit(p), fn); }
  eventPattern(p, fn) { this.#hodisalar.set(kalit(p), fn); }
  qabul({ pattern, data, id }) {
    if (id === undefined) { // hodisa: javob yo'q
      const fn = this.#hodisalar.get(pattern);
      if (!fn) console.log(`ERROR noma'lum hodisa: ${pattern}`);
      else fn(data);
      return undefined;
    }
    const fn = this.#xabarlar.get(pattern);
    if (!fn) {
      return { id, err: "There is no matching message handler" };
    }
    return { id, response: fn(data) };
  }
}

class Mijoz {
  #id = 0;
  constructor(server) { this.server = server; }
  async send(p, data) {
    const paket = tarmoq({ pattern: kalit(p), data, id: ++this.#id });
    const javob = tarmoq(this.server.qabul(paket));
    if (javob.err) throw new Error(javob.err);
    return javob.response;
  }
  emit(p, data) {
    this.server.qabul(tarmoq({ pattern: kalit(p), data }));
  }
}

const s = new Server();
s.messagePattern({ cmd: "statistika", v: 1 }, (d) => ({
  egasi: d.egasi, jami: 12, vaqt: new Date(0),
}));
s.eventPattern("vazifa.bajarildi", (d) => {
  console.log(`hodisa: ${d.id}`);
});

const m = new Mijoz(s);
console.log(await m.send({ v: 1, cmd: "statistika" }, { egasi: 7 }));
m.emit("vazifa.bajarildi", { id: 7 });
m.emit("vazifa.ochirildi", { id: 7 });
await m.send({ cmd: "yoq" }, {}).catch((e) => console.log(e.message));

Konsolda:

text
{ egasi: 7, jami: 12, vaqt: '1970-01-01T00:00:00.000Z' }
hodisa: 7
ERROR noma'lum hodisa: vazifa.ochirildi
There is no matching message handler

Birinchi qatordagi vaqt ga qarang: xizmat Date qaytardi, mijoz satr oldi. Tarmoqdan faqat JSON o'tadi — sana, Map, klass nusxasi, BigInt yo'qoladi yoki xato beradi. Nest'ning TCP transporti ham JSON ishlatadi. Shuning uchun xizmatlar orasidagi shartnomani (DTO) oddiy JSON turlarida yozing. Qolgan qatorlar Nest chiqishi bilan bir xil ma'noda: { v: 1, cmd } kalitlar tartibidan qat'i nazar topildi, noma'lum hodisa — xizmat logida, noma'lum xabar — mijozda xato.

Soddalashtirilgan joyi: haqiqiy transport asinxron va javoblar tartibsiz kelishi mumkin — shuning uchun id kerak (3-mashq).

9. Kanon tajribasi: nega loyihaga kirmaydi

Kanon vazifalar-api ni xizmatlarga bo'lmadi. Tajribada (@nestjs/microservices 12.2.0, TCP, brokersiz) xuddi shu darsdagi beshta narsa tekshirilgan. Chiqishni darsdagi demo bilan solishtiring — raqamlar yaqin, xatolar matni aynan:

text
2) TCP mikroservis: send → {"egasi":1,"jami":12,"bajarilgan":5} (5.4 ms, birinchi)
   1000 ketma-ket so'rov-javob: o'rtacha 0.118 ms
   [mikroservis] voqea qabul qilindi: {"id":7}
   timeout(100) → TimeoutError
   noma'lum pattern → "There is no matching message handler defined in the remote service."

(Kanon chiqishidagi "voqea" — tajriba faylidagi matn; kursda biz "hodisa" deymiz.) Sabablar oddiy: bitta jamoa, bitta frontend, bitta baza, yuk kichik. Mikroservisning birorta foydasi ishlamaydi, narxi esa to'liq to'lanadi: taqsimlangan tranzaksiya, har chaqiruvda tarmoq, ko'p deploy. 29-qismda qo'shilgan ishchi jarayon (BullMQ worker) — loyihadagi yagona "alohida jarayon", u ham navbat orqali, mikroservis transporti emas.

10. Ko'p uchraydigan xatolar

Xato Nima bo'ladi Tuzatish
send ga obuna yo'q xabar umuman ketmaydi return yoki firstValueFrom
send da timeout yo'q xizmat osilsa, gateway ham osiladi .pipe(timeout(ms))
Oddiy Error dan aniq xabar kutish mijoz Internal server error oladi RpcException
Javob kerak, lekin emit javob yo'q, xato ham yo'q send
Date/Map ni tarmoqdan o'tkazish satr yoki bo'sh obyekt keladi JSON turlari
Mikroservis "zamonaviy" deb murakkablik, sekinlik avval modular monolit

11. Mashqlar

Mashqlar kurs/mashqlar/30/45-mikroservis/ da.

1-mashq (oson): bo'shliqlarni to'ldiring

  • So'rov-javob handler'i dekoratori —
  • Hodisa yuboradigan ClientProxy metodi —
  • Obuna bo'lmaguncha xabar yubormaydigan metod —
  • Mijozga aniq xato obyektini yetkazadigan istisno klassi —
  • HTTP va mikroservisni bitta ilovada birlashtirish — ilova
Yechim
  • @MessagePattern — @MessagePattern({ cmd: "statistika" }); hodisa uchun @EventPattern.
  • emit — darhol ketadi, javob yo'q.
  • send — sovuq Observable; firstValueFrom bilan obuna.
  • RpcException — oddiy Error esa Internal server error ga aylanadi.
  • hybrid — connectMicroservice + startAllMicroservices.

2-mashq (o'rta): timeout modeli

vaqtCheklovi(promise, ms) funksiyasini yozing: ms ichida natija kelmasa, name i "TimeoutError" bo'lgan xato bilan rad etsin (rxjs timeout dagidek). Taymerni natija kelganda ham tozalang. Ikki "xizmat" bilan sinang: tez (5 ms) va sekin (200 ms), chegara 50 ms. Ishora: Promise.race (Promise kombinatorlari) va finally.

Yechim
js
function vaqtCheklovi(promise, ms) {
  let t;
  const muddat = new Promise((_, rad) => {
    t = setTimeout(() => {
      const x = new Error("Timeout has occurred");
      x.name = "TimeoutError";
      rad(x);
    }, ms);
  });
  return Promise.race([promise, muddat])
    .finally(() => clearTimeout(t));
}

const kut = (ms, q) =>
  new Promise((ok) => setTimeout(() => ok(q), ms));
const xizmatlar = {
  sekin: () => kut(200, "kech"),
  tez: () => kut(5, "tez"),
};

for (const nom of ["tez", "sekin"]) {
  try {
    console.log(nom, "→", await vaqtCheklovi(xizmatlar[nom](), 50));
  } catch (e) {
    console.log(nom, "→", e.name);
  }
}

Konsolda:

text
tez → tez
sekin → TimeoutError

"Timeout has occurred" — rxjs'dagi TimeoutError ning xabari. finally siz tez holatida taymer 50 ms osilib turardi. Darsdagi kabi: sekin xizmat baribir 200 ms ishladi — timeout uni to'xtatmaydi, faqat kutmaydi.

3-mashq (qiyin): javoblarni id bo'yicha moslash

Bitta TCP ulanish orqali uchta so'rov ketdi, javoblar esa boshqa tartibda qaytdi (har biri har xil vaqt ishladi). Mijoz qaysi javob qaysi so'rovniki ekanini qanday biladi? Mijoz klassini yozing: send(data) har so'rovga yangi id beradi va Map da id → resolve saqlaydi; javob kelganda id bo'yicha to'g'ri Promise ni hal qiladi. Server a ni 30 ms, b ni 5 ms, c ni 15 ms da qaytarsin. Ishora: #kutilmoqda — Map; Promise.all natijasi so'rovlar tartibida.

Yechim
js
// Javoblar tartibsiz keladi — mijoz ularni id bo'yicha moslaydi
const kut = (ms) => new Promise((ok) => setTimeout(ok, ms));

function server(paket, javobBer) {
  const kechikish = { a: 30, b: 5, c: 15 }[paket.data];
  kut(kechikish).then(() => javobBer({
    id: paket.id, response: paket.data.toUpperCase(),
  }));
}

class Mijoz {
  #id = 0;
  #kutilmoqda = new Map(); // id → resolve
  send(data) {
    const id = ++this.#id;
    return new Promise((ok) => {
      this.#kutilmoqda.set(id, ok);
      server({ id, data }, (j) => this.#keldi(j));
    });
  }
  #keldi({ id, response }) {
    console.log(`keldi: id=${id}`);
    this.#kutilmoqda.get(id)(response);
    this.#kutilmoqda.delete(id);
  }
}

const m = new Mijoz();
const sorovlar = ["a", "b", "c"].map((x) => m.send(x));
const natija = await Promise.all(sorovlar);
console.log(natija);

Konsolda:

text
keldi: id=2
keldi: id=3
keldi: id=1
[ 'A', 'B', 'C' ]

Javoblar 2, 3, 1 tartibida keldi, natija esa to'g'ri: A, B, C. Nest'ning TCP mijozi aynan shunday ishlaydi: har paketda id, javobda o'sha id. Darsdagi "1000 parallel: 48 ms" o'lchovi shu mexanizm tufayli mumkin — bitta ulanishda minglab so'rov bir vaqtda "havoda" turadi. delete muhim: usiz Map har so'rov bilan o'sib boradi (xotira sizishi).

12. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi

vazifalar-api o'zgarmaydi. Mashq sifatida: o'z demo loyihangizda VazifalarService ning statistikasini alohida "hisobot" xizmatiga chiqarib, gateway'dan send + timeout bilan chaqiring. Keyin hisobot xizmatini o'chirib, gateway nima qaytarishini ko'ring — bu mikroservis dunyosidagi birinchi "xizmat yo'q" holati. Keyingi darsda broker transportlar shu holatni qanday yumshatishini ko'ramiz.

13. Real ishda

  • Qayerda uchraydi: katta e-commerce, bank va taksi platformalarida — "buyurtma", "to'lov", "bildirishnoma" alohida xizmatlar. Nest mikroservislari Node ekotizimida eng mashhur yechimlardan biri.
  • Hybrid — eng ko'p uchraydigan real holat: HTTP API + RabbitMQ yoki Kafka'dan hodisalar tinglash bitta ilovada.
  • Intervyu savollari: "Monolit va mikroservis — qachon nima?", "send va emit farqi?", "Mikroservis chaqiruvida timeout nega shart?", "API gateway nima?".
  • Kod review'da: "bu send ga timeout qo'yilganmi?" va "bu xato mijozga nima bo'lib boradi?".

Xulosa

  • Mikroservis — tarmoq orqali gaplashadigan alohida jarayonlar; foydasi — mustaqillik, narxi — tarmoq, taqsimlangan tranzaksiya, murakkablik.
  • createMicroservice + @MessagePattern (so'rov-javob) va @EventPattern (hodisa); pattern kalitlari saralangan satrga aylanadi.
  • ClientProxy.send — sovuq, obunasiz ketmaydi; emit — darhol, javobsiz; noma'lum hodisa xizmat logida qoladi.
  • RpcException — aniq xato mijozga; oddiy Error — Internal server error; har send ga timeout.
  • TCP: ~0.1 ms bir so'rov (lokal) — metod chaqiruvidan ming baravarlab sekin; hybrid ilova — HTTP + transport bir jarayonda.

Keyingi dars: Transport'lar chuqur — Redis, NATS, RabbitMQ, Kafka va gRPC transportlari, serializer, retry va ishonchlilik sozlamalari.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Mikroservis asoslari NestJS'da: @MessagePattern, @EventPattern va ClientProxy — IlmHamroh