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.
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.
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.
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.
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.
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.
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.
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);
}
Transport — kod bir xil, faqat sozlama o‘zgaradi
NestJS bularni transport orqali uzatadi. Eng kuchli tomoni: biznes kodingni o‘zgartirmaysan, faqat transportni almashtirsan.
| Transport | Qachon | Xususiyat |
|---|---|---|
TCP | Oddiy ichki request-response | Brokersiz, eng sodda boshlanish |
RMQ (RabbitMQ) | Event, ishonchli yetkazish | Navbat, qayta urinish, ack |
Kafka | Yuqori hajmli event oqimi, log | Saqlanadigan, qayta o‘qiladigan |
gRPC | Tez, qat’iy kontraktli aloqa | Protobuf sxema, ikki tomon tilidan mustaqil |
NestJS implementatsiya
@nestjs/microservicesEndi to‘liq tizimni quramiz: 1 ta Gateway (HTTP) + 3 ta mikroservis. Gateway ↔ Users/Orders — TCP (sinxron). Orders → Notification — RabbitMQ (event).
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);
}
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.
Resilience — nosozlikka bardosh
timeout · retry · circuit breakerNega 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.
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.
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' })),
);
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.
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
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.
Saga — taqsimlangan tranzaksiya
orchestration · choreographyNega 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.
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.
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).
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);
}
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
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.
Productionga tayyorlik cheklisti
Bu darslik tushuncha va ishlaydigan asosni beradi. “Ishlaydi”dan “productionda ishonchli”gacha bo‘lgan farq — quyidagilar:
| Mavzu | Nima qo‘shiladi |
|---|---|
| Database-per-service | Har xizmatga alohida Postgres + Prisma (xotira massivi emas) |
| Resilience | timeout + retry + circuit breaker + fallback — har tarmoq chaqirig‘ida (5-qism) |
| Distributed transaction | Saga + kompensatsiya + Idempotency + Outbox (6-qism) |
| Observability | Har so‘rovga correlation-id, OpenTelemetry bilan distributed tracing |
| Validation | class-validator + ValidationPipe (gateway darajasida) |
| Auth | Gateway’da JWT tekshiruvi (har xizmat ichida emas) |
| Config | @nestjs/config — portlar/URL’lar .envda |
| Graceful shutdown | app.enableShutdownHooks() — ulanishlarni toza yopish |
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 joydaretry+backoffvacircuit breaker, doimfallback. - 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.