Tizim dizayni kursi · 1-mavzu
TuzdiDinMuhammad

Mikroservislar — noldan professional darajagacha

Monolit og‘rig‘idan boshlab, NestJS bilan ishlaydigan tizim quramiz; so‘ng uni productionga olib chiqadigan ikki muhim mavzu — Resilience (nosozlikka bardosh) va Saga (taqsimlangan tranzaksiya) — bilan to‘ldiramiz.

Stack: NestJS · @nestjs/microservices Aloqa: TCP · RabbitMQ Naqshlar: Circuit Breaker · Saga · Outbox

Bu darsda nima bor

1–4-qismlar mikroservisni noldan quradi (nega → g‘oya → aloqa → kod). 5–6-qismlar uni productionga tayyorlaydi: Resilience va Saga — aynan shu loyiha ustida, kod bilan. Har bir kod bloki to‘g‘ridan-to‘g‘ri NestJS’da ishlatish uchun.

01
Mikroservislar · qism 1

Nega kerak — og‘riqdan tushunish

🏭

Oddiy o‘xshatish: bitta ulkan zavod o‘rniga — har biri bitta detalga ixtisoslashgan ko‘plab mustaqil ustaxonalar. Biri to‘xtasa, qolganlari ishlayveradi; biriga ish ko‘paysa — faqat o‘shani kengaytirasan.

Avval monolitni tasavvur qil: bitta NestJS loyiha, ichida UsersModule, OrdersModule, PaymentsModule — hammasi bitta jarayonda, bitta postgresga ulangan. Boshida bu ajoyib: tez yoziladi, tranzaksiya oson, deploy bitta.

Muammo (4 ta og‘riq)

1. Paymentda kichik tuzatish kiritsang ham — butun ilovani qayta deploy qilasan.
2. Catalogga sekundiga 10 000 so‘rov kelyapti, lekin uni alohida kuchaytirib bo‘lmaydi — hamma narsani ko‘paytirishga majbursan.
3. Bitta modulda memory leak bo‘lsa — butun jarayon qulaydi.
4. 5 ta jamoa bitta kod bazasida bir-biriga xalaqit beradi.

Yechim

Ilovani kichik, mustaqil xizmatlarga bo‘lish. Mikroservis aynan shu 4 og‘riqni hal qiladi: mustaqil deploy, mustaqil scaling, nosozlik izolyatsiyasi, jamoa mustaqilligi. Boshqa hamma narsa — shu 4 tasining bonusi.

02
Mikroservislar · qism 2

Asosiy g‘oya va Database-per-service

Mikroservis = o‘z kodi + o‘z ma’lumotlar bazasi + o‘z deploy’i; boshqalar bilan faqat tarmoq orqali gaplashadi.

Eng ko‘p qilinadigan xato

Chegarani texnik qatlam bo‘yicha chizish (controller-service, repo-service). Bu noto‘g‘ri — natijada bitta amal uchun 5 ta xizmatga borishga to‘g‘ri keladi.

To‘g‘ri yondashuv

Chegarani biznes domeni bo‘yicha chiz (DDD — bounded context). Orders xizmati buyurtma bilan bog‘liq hamma narsani (logika + ma’lumot) o‘zida saqlaydi.

Eng muhim qoida — Database-per-service

Har bir xizmat o‘z bazasiga egalik qiladi. Orders xizmati users jadvaliga SELECT qilmaydi — Users xizmatining API’si orqali so‘raydi. Agar ikki xizmat bitta bazaga tegsa, bu mikroservis emas — “taqsimlangan monolit”, ya’ni mikroservis murakkabligi-yu monolit bog‘liqligini birga olgan eng yomon variant.

Orders xizmati users jadvaligato‘g‘ridan SELECT ✕ Users API orqali ✓ Users · o‘z DB
Boshqa xizmat bazasiga to‘g‘ridan tegish ✕ · faqat API orqali ✓
03
Mikroservislar · qism 3

Qanday gaplashadi — Sync va Async

Mikroservis dizaynining yuragi — aloqa. Ikki xil bor, NestJS ikkalasini ham qo‘llaydi.

SINXRON  Request–Response — javob kerak bo‘lganda

Gateway buyurtma yaratishdan oldin foydalanuvchi bor-yo‘qligini so‘raydi va javobni kutadi. NestJS’da send() + @MessagePattern.

// Gateway — so'rov yuboradi (Observable qaytaradi), JAVOB kutadi
this.usersClient.send({ cmd: 'get_user' }, userId);

// Users xizmati — javob beradi
@MessagePattern({ cmd: 'get_user' })
getUser(@Payload() id: string) {
  return this.service.findOne(id);   // qaytgan qiymat javob bo'lib boradi
}

ASINXRON  Event — javob kerak emas

Buyurtma yaratilgach order_created hodisasini tashlaysan; kim qiziqsa o‘zi ushlaydi (email, analitika). NestJS’da emit() + @EventPattern.

// Orders — hodisa tashlaydi, javob KUTMAYDI ("e'lon qil va unut")
this.client.emit('order_created', { orderId, userId });

// Notification — tinglaydi, hech narsa qaytarmaydi
@EventPattern('order_created')
handle(@Payload() data) {
  this.email.sendConfirmation(data);
}
SINXRON (send) — javob kutadi Gateway so‘rov javob ↩ Users ASINXRON (emit) — kutmaydi Orders broker davom etaveradi
send = javobni kutaman · emit = e’lon qilaman va davom etaman

Transport — kod bir xil, faqat sozlama o‘zgaradi

NestJS bularni transport orqali uzatadi. Eng kuchli tomoni: biznes kodingni o‘zgartirmaysan, faqat transportni almashtirsan.

TransportQachonXususiyat
TCPOddiy ichki request-responseBrokersiz, eng sodda boshlanish
RMQ (RabbitMQ)Event, ishonchli yetkazishNavbat, qayta urinish, ack
KafkaYuqori hajmli event oqimi, logSaqlanadigan, qayta o‘qiladigan
gRPCTez, qat’iy kontraktli aloqaProtobuf sxema, ikki tomon tilidan mustaqil
04
Mikroservislar · qism 4

NestJS implementatsiya

@nestjs/microservices

Endi to‘liq tizimni quramiz: 1 ta Gateway (HTTP) + 3 ta mikroservis. Gateway ↔ Users/Orders — TCP (sinxron). Orders → Notification — RabbitMQ (event).

API Gateway (HTTP) TCP send Users (3001) Orders (3002) emit (RabbitMQ) Notification DB DB
Gateway HTTP ochadi · ichki xizmatlar faqat transport orqali gaplashadi

1 · Mikroservisni ishga tushirish (Users)

// users/src/main.ts — DIQQAT: create EMAS, createMicroservice (HTTP ochmaydi)
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
  transport: Transport.TCP,
  options: { host: '0.0.0.0', port: 3001 },
});
await app.listen();
// users/src/users.controller.ts
@Controller()
export class UsersController {
  @MessagePattern({ cmd: 'get_user' })       // "manzil" — Gateway shunga yuboradi
  getUser(@Payload() id: string) {
    return DB.find((u) => u.id === id) ?? null;
  }
  @MessagePattern({ cmd: 'create_user' })
  createUser(@Payload() dto: { name: string; email: string }) {
    const user = { id: String(DB.length + 1), ...dto };
    DB.push(user);
    return user;
  }
}

2 · Orders buyurtma yaratib, event tashlaydi

// orders/src/orders.controller.ts
@MessagePattern({ cmd: 'create_order' })
createOrder(@Payload() dto: { userId: string; total: number }) {
  const order = { id: String(ORDERS.length + 1), ...dto, status: 'created' };
  ORDERS.push(order);
  // ASINXRON: Notification ishlamay tursa ham, buyurtma yaratilaveradi
  this.notify.emit('order_created', { orderId: order.id, userId: order.userId });
  return order;
}

3 · Notification eventni tinglaydi

// notification/src/notification.controller.ts
@EventPattern('order_created')
async handle(@Payload() data: any, @Ctx() ctx: RmqContext) {
  console.log(`📧 Email yuborildi: buyurtma #${data.orderId}`);
  // ishlangach qo'lda tasdiqlaymiz (xabar yo'qolmasligi uchun)
  ctx.getChannelRef().ack(ctx.getMessage());
}

4 · Gateway hammasini bog‘laydi

// gateway/src/app.controller.ts
@Post('orders')
async createOrder(@Body() dto: { userId: string; total: number }) {
  // PRO: buyurtmadan oldin foydalanuvchini tekshiramiz (sinxron so'rov)
  const user = await firstValueFrom(this.users.send({ cmd: 'get_user' }, dto.userId));
  if (!user) return { error: 'Bunday foydalanuvchi yo‘q' };
  return this.orders.send({ cmd: 'create_order' }, dto);
}
To‘liq ishga tushiriladigan loyiha

docker-compose (RabbitMQ), barcha main.ts/module fayllar, npm i paketlar va curl bilan sinash qadamlari — alohida lab faylida (01-microservices-nestjs-lab.md) to‘liq berilgan.

05
Chuqurlashtirilgan mavzu

Resilience — nosozlikka bardosh

timeout · retry · circuit breaker
🛟

Nega bu mavzu mikroservisning ajralmas qismi: monolitda funksiya chaqirig‘i bir zumda va ishonchli. Mikroservisda har bir chaqiruv — tarmoq orqali, ya’ni sekinlashishi yoki umuman javob bermasligi mumkin. Buni hisobga olmaslik — eng katta xato.

Muammo — zanjirli qulash (cascading failure)

Gateway → Orders → Payment zanjirida Payment sekinlashdi (3s emas, 30s javob beryapti). Gateway kutadi, ulanishlar to‘ladi, yangi so‘rovlar ham tiqiladi va bitta sekin xizmat butun tizimni qulatadi. Bitta kichik nosozlik butun saytni yiqitadi.

Yechim — har bir tarmoq chaqirig‘ini himoyalash

Hech bir tashqi chaqiruvni “himoyasiz” qoldirma. Asosiy 4 qatlam: Timeout (cheksiz kutma) → Retry + backoff (aqlli qayta urinish) → Circuit Breaker (yiqilgan xizmatga chaqiruvni to‘xtatish) → Fallback (zaxira javob).

1–3 · Timeout + Retry + Fallback (RxJS bilan)

NestJS’ning send() metodi RxJS Observable qaytargani uchun, himoyani .pipe() bilan ulab qo‘yamiz — juda tabiiy:

import { timeout, retry, catchError } from 'rxjs/operators';
import { of, timer } from 'rxjs';

this.users.send({ cmd: 'get_user' }, id).pipe(
  // 1) TIMEOUT — 3 soniyada javob bo'lmasa, uzamiz (cheksiz kutmaymiz)
  timeout(3000),

  // 2) RETRY + EKSPONENSIAL BACKOFF + JITTER
  retry({
    count: 3,
    delay: (err, n) => {
      const base = Math.min(1000 * 2 ** n, 8000);     // 2s, 4s, 8s
      const jitter = Math.random() * 300;             // bir vaqtda urilmasin
      return timer(base + jitter);
    },
  }),

  // 3) FALLBACK — baribir bo'lmasa, muloyim zaxira javob (qulamaymiz)
  catchError(() => of({ id, name: 'Mehmon' })),
);
Nega backoff + jitter?

Oddiy retry yiqilgan xizmatni yana bombardimon qiladi va battar yiqitadi. Backoff — har urinishda kutish vaqtini oshiradi (2s→4s→8s), xizmatga nafas oldiradi. Jitter — tasodifiy qo‘shimcha, minglab klient bir vaqtda urilib qolmasligi uchun.

4 · Circuit Breaker (zanjir uzgich)

Retry vaqtinchalik nosozlikka yaxshi. Lekin xizmat butunlay yiqilgan bo‘lsa, har so‘rovda 3 marta urinish — vaqt va resurs isrofi. Circuit Breaker buni hal qiladi: ketma-ket xato ko‘rsa, zanjirni ochadi va bir muddat umuman urinmaydi — darhol fallback qaytaradi.

CLOSEDnormal — o‘tkazadi ko‘p xato OPENbloklaydi → fallback resetTimeout HALF-OPEN1 ta sinov so‘rovi tuzaldi → CLOSED
OPEN holatda chaqiruvlar darhol fallback oladi — yiqilgan xizmat tinch qoladi
import CircuitBreaker from 'opossum';   // npm i opossum

// chaqiruvni "himoya qobig'i"ga o'raymiz
const call = (id: string) =>
  firstValueFrom(this.users.send({ cmd: 'get_user' }, id));

const breaker = new CircuitBreaker(call, {
  timeout: 3000,                  // sekin javob = xato deb hisoblanadi
  errorThresholdPercentage: 50,   // 50% so'rov xato bo'lsa -> OPEN
  resetTimeout: 10000,            // 10s dan keyin HALF-OPEN (sinov)
});

breaker.fallback(() => ({ name: 'Mehmon' }));   // OPEN holatda zaxira javob

const user = await breaker.fire(id);   // breaker o'zi qaror qiladi
Amaliy qoida

Har bir tashqi xizmat / mikroservis chaqirig‘ida — kamida timeout. So‘ng vaqtinchalik xatolar uchun retry + backoff. Muhim yoki tez-tez yiqiladigan bog‘liqliklarga — circuit breaker. Va doim fallback: “sayt yiqildi” o‘rniga muloyim, qisman javob.

06
Chuqurlashtirilgan mavzu

Saga — taqsimlangan tranzaksiya

orchestration · choreography
🔗

Nega bu mikroservisda muqarrar: “buyurtma yaratish” aslida 3 ta xizmatga tegadi — Ombor, To‘lov, Buyurtma. Har birining o‘z bazasi bor (database-per-service!). Demak oddiy BEGIN…COMMIT ACID tranzaksiyasi ishlamaydi — u bitta baza ichida ishlaydi, tarmoq orqali emas.

Muammo

To‘lov muvaffaqiyatli o‘tdi, lekin ombor “mahsulot tugagan” deb xato berdi. Endi pul yechildi, mahsulot yo‘q. Yoki teskarisi. Bir amal bir nechta xizmatni qamraydi va ularning hammasi muvaffaqiyatli yoki hammasi bekor bo‘lishi kerak — lekin global ACID yo‘q.

Yechim — Saga

Bitta katta tranzaksiya o‘rniga — ketma-ket lokal tranzaksiyalar. Har biri o‘z bazasida muvaffaqiyatli bajariladi va keyingisini boshlaydi. Biror qadam yiqilsa — oldingilarini kompensatsiya amallari (teskari, bekor qiluvchi) bilan ortga qaytaradi. Natija — pirovard izchillik (to‘liq ACID emas).

1. Ombor rezerv 2. To‘lov olish 3. Tasdiqlash 2-qadam XATO → Kompensatsiya: rezervni bekor qil (teskari yo‘nalish)
Xato bo‘lsa — bajarilgan qadamlar teskari tartibda bekor qilinadi

Yondashuv 1 · Orkestratsiya (markaziy dirijyor)

Bitta “dirijyor” barcha qadamlarni ketma-ket boshqaradi va xatoda kompensatsiyani chaqiradi. Mantiq bir joyda — kuzatish va debug oson. Bu eng ko‘p ishlatiladigan, tushunarli usul:

// orders/src/order-saga.ts — ORKESTRATSIYA (markaziy dirijyor)
async createOrder(dto: { userId: string; items: any[]; total: number }) {
  // bajarilgan qadamlarni "bekor qilish" funksiyalari to'planadi
  const compensations: Array<() => Promise<void>> = [];

  try {
    // 1-qadam: omborda rezerv
    const r = await firstValueFrom(this.inventory.send({ cmd: 'reserve' }, dto.items));
    compensations.push(() => firstValueFrom(this.inventory.send({ cmd: 'release' }, r.id)));

    // 2-qadam: to'lov
    const p = await firstValueFrom(this.payment.send({ cmd: 'charge' }, { userId: dto.userId, total: dto.total }));
    compensations.push(() => firstValueFrom(this.payment.send({ cmd: 'refund' }, p.id)));

    // 3-qadam: buyurtmani tasdiqlash
    const order = await firstValueFrom(this.orders.send({ cmd: 'confirm' }, dto));
    return order;                       // ✓ saga muvaffaqiyatli

  } catch (err) {
    // ✕ xato — bajarilganlarni TESKARI tartibda bekor qilamiz
    for (const undo of compensations.reverse()) {
      try { await undo(); }
      catch (e) { /* kompensatsiya ham xato -> ALERT + qo'lda aralashuv */ }
    }
    throw new Error('Buyurtma bekor qilindi (saga rollback)');
  }
}

Yondashuv 2 · Xoreografiya (markazsiz, event orqali)

Markaz yo‘q — har bir xizmat hodisani tinglab, o‘z qadamini bajaradi va keyingi hodisani tashlaydi. Ko‘proq bo‘shashgan (decoupled), lekin oqimni kuzatish qiyinroq:

// XOREOGRAFIYA — markaz yo'q, har xizmat keyingisini event bilan boshlaydi

// Orders:
this.client.emit('order_created', { orderId, userId, total });

// Payment: tinglaydi -> to'lov -> natijani e'lon qiladi
@EventPattern('order_created')
async onOrderCreated(@Payload() e) {
  try {
    await this.charge(e.userId, e.total);
    this.client.emit('payment_succeeded', { orderId: e.orderId });
  } catch {
    this.client.emit('payment_failed', { orderId: e.orderId });  // kompensatsiya tetigi
  }
}

// Orders: to'lov yiqilsa -> buyurtmani bekor qiladi (kompensatsiya)
@EventPattern('payment_failed')
async onPaymentFailed(@Payload() e) {
  await this.cancelOrder(e.orderId);
}
Ikkita majburiy hamroh: Idempotency + Outbox

Idempotency: tarmoq retry qilganda bir hodisa 2 marta yetishi mumkin — har hodisaga eventId berib, ishlangani bazada belgilanadi, takrori o‘tkazib yuboriladi. Outbox pattern: hodisani biznes ma’lumoti bilan bitta lokal tranzaksiyada “outbox” jadvaliga yozasan, keyin alohida jarayon uni brokerga uzatadi — shunda “baza yozildi-yu, event yo‘qoldi” holati bo‘lmaydi.

Orkestratsiya
  • Mantiq bir joyda — oson kuzatiladi
  • Murakkab oqim uchun qulay
  • Kompensatsiya tartibi aniq
Xoreografiya
  • Eng bo‘shashgan, mustaqil xizmatlar
  • Lekin umumiy oqimni ko‘rish qiyin
  • Tsiklik bog‘liqlik xavfi bor
Amaliy qoida

Birinchi tavsiya: bunday holatdan iloji boricha qoch — chegaralarni shunday chizki, bitta amal bitta xizmatda tugasin. Zarur bo‘lsa — deyarli har doim Saga (2PC emas). Boshlash uchun — orkestratsiya, oqim murakkablashganda xoreografiyaga o‘tasan.

07
Yakuniy

Productionga tayyorlik cheklisti

Bu darslik tushuncha va ishlaydigan asosni beradi. “Ishlaydi”dan “productionda ishonchli”gacha bo‘lgan farq — quyidagilar:

MavzuNima qo‘shiladi
Database-per-serviceHar xizmatga alohida Postgres + Prisma (xotira massivi emas)
Resiliencetimeout + retry + circuit breaker + fallback — har tarmoq chaqirig‘ida (5-qism)
Distributed transactionSaga + kompensatsiya + Idempotency + Outbox (6-qism)
ObservabilityHar so‘rovga correlation-id, OpenTelemetry bilan distributed tracing
Validationclass-validator + ValidationPipe (gateway darajasida)
AuthGateway’da JWT tekshiruvi (har xizmat ichida emas)
Config@nestjs/config — portlar/URL’lar .envda
Graceful shutdownapp.enableShutdownHooks() — ulanishlarni toza yopish
08
Yakuniy

Eslab qolish kerak bo‘lgan 5 jumla

  • Mikroservis = mustaqil deploy + scaling + DB + jamoa. Boshqasi — bonus.
  • Chegarani biznes domeni bo‘yicha chiz; database-per-service buzilsa — “taqsimlangan monolit”, eng yomon holat.
  • send() = javob kutaman (sinxron) · emit() = event (kutmayman). Kod bir xil, faqat transport o‘zgaradi.
  • Tarmoq ishonchsiz: har chaqiruvga timeout, kerak joyda retry+backoff va circuit breaker, doim fallback.
  • Bir amal ko‘p xizmatni qamrasa — global ACID yo‘q. Saga + kompensatsiya + idempotency + outbox.

Keyingi mavzu tayyor bo‘lsa, menga shunchaki yozing:

“2-mavzu: Modulli monolit” — bu mikroservisning aynan aksi, ikkalasini solishtirib ko‘rasiz.