Tizim dizayni kursi · 2-mavzu
TuzdiDinMuhammad

Modulli monolit — soddalik bilan tartib

Mikroservisning aksi: bitta deploy, lekin ichida aniq chegarali modullar. NestJS bilan toza modul chegaralarini quramiz, ichki event qo‘shamiz va — eng muhimi — uni kelajakda mikroservisga og‘riqsiz ajratish yo‘lini ko‘rsatamiz.

Stack: NestJS · Prisma (multiSchema)Naqshlar: Encapsulation · Ports & AdaptersBog‘liq: 1-mavzu (Mikroservis)

Bu darsda nima bor

1–4-qismlar modulli monolitni noldan quradi: g‘oya, 3 oltin qoida va NestJS implementatsiya. 5–6-qismlar — chuqurlashtirilgan: ichki event va mikroservisga pog‘onali ko‘chirish. 7-qism mikroservis bilan to‘liq taqqoslab, qaysi birini tanlashni hal qiladi.

01
Modulli monolit · qism 1

Nega kerak — ikki chekka muammosi

🏢

Oddiy o‘xshatish: bitta bino, lekin ichida aniq devorlar bilan ajratilgan bo‘limlar. Hamma bir tomda (bitta deploy), lekin har kim o‘z xonasida (aniq chegara). Kerak bo‘lsa, keyin bir bo‘limni alohida binoga (mikroservisga) ko‘chirish oson.

1-mavzuda mikroservisni ko‘rdik. Lekin kichik jamoa uchun u juda murakkab va qimmat: tarmoq, taqsimlangan tranzaksiya, DevOps, monitoring — hammasi birdan yelkangga tushadi. Ikkinchi tomonda esa oddiy monolit bor.

Muammo — ikki chekka ham yomon

Tartibsiz monolit vaqt o‘tib “loy to‘pi” (big ball of mud)ga aylanadi: hamma narsa bir-biriga chigallashadi, bitta joyga teginsang boshqa yerda buziladi, hech kim oqibatni bilmaydi. Mikroservis esa erta tanlansa — keraksiz murakkablik, sekin rivojlanish va “taqsimlangan loy to‘pi”.

Yechim — oltin o‘rta

Modulli monolit: ilova bitta loyiha va bitta deploy bo‘lib qoladi (monolitning soddaligi), lekin ichida aniq chegarali modullarga bo‘linadi (mikroservisning tartibi). Ya’ni mikroservisning mantiqiy chegaralarini olamiz, lekin tarmoq murakkabligisiz.

Tartibsiz monolit“loy to‘pi” 🌀 chegara yo‘q Modulli monolit✅ oltin o‘rta bitta deploy + chegara Mikroserviskuchli, lekin og‘ir tarmoq murakkabligi
Modulli monolit — soddalik bilan tartibni birlashtiradi
02
Modulli monolit · qism 2

Asosiy g‘oya va 3 oltin qoida

Modul = bitta biznes domeni (bounded context): Users, Orders, Billing, Catalog. Har bir modul o‘z ichida to‘liq: controller + service + repository + tiplar.

3 ta oltin qoida

1. Yagona public eshik. Modul tashqariga faqat bitta serviceni ochadi (masalan UsersService). Repository, entity, ichki yordamchilar — yashirin.

2. Modullar faqat public service orqali gaplashadi. Bir modul boshqasining ichki kodi yoki jadvaliga hech qachon to‘g‘ridan-to‘g‘ri tegmaydi.

3. Har modul o‘z ma’lumotiga egalik qiladi. Bitta baza, lekin har modulning o‘z sxemasi/jadvallari bor; modul faqat o‘ziniki bilan ishlaydi.

NestJS aynan shu uchun yaratilgan — modul tabiiy ravishda enkapsulyatsiya beradi. Providerlar standart holatda private; faqat exportsdagilar ko‘rinadi:

// modules/users/users.module.ts
@Module({
  providers: [UsersService, UsersRepository],  // ikkalasi ham modul ICHIDA
  exports: [UsersService],                      // FAQAT shu tashqariga ko'rinadi
})
export class UsersModule {}
// UsersRepository eksport QILINMAGAN -> boshqa modul uni inject qila OLMAYDI
Nega bu muhim

Aynan shu enkapsulyatsiya “loy to‘pi”ning oldini oladi. Repository eksport qilinmagani uchun, hatto xohlasang ham boshqa modulda UsersRepositoryni chaqira olmaysan — kompilyator ruxsat bermaydi. Chegara kod darajasida majburlanadi, intizomga tayanmaydi.

03
Modulli monolit · qism 3

Chegarani majburlash

encapsulation · boundaries

Eng katta xavf — vaqt o‘tib chegaralar buzilib, yana “loy to‘pi”ga aylanish. Buni oldini olishning aniq texnikalari bor.

1 · Barrel fayl — public kontrakt

Har modulga index.ts qo‘yib, tashqariga nimaning ko‘rinishini bitta joyda belgilaysan. Qolgani import qilinmaydi:

// modules/users/index.ts — modulning YAGONA "public kontrakti"
export { UsersModule } from './users.module';
export { UsersService } from './users.service';
export type { User } from './user.types';
// repository, entity, mapper, internal util — BU YERDA YO'Q (yashirin)

2 · Modullararo aloqa — faqat public service orqali

// modules/orders/orders.module.ts
@Module({
  imports: [UsersModule],            // Users modulining public eshigini olamiz
  providers: [OrdersService, OrdersRepository],
})
export class OrdersModule {}

// modules/orders/orders.service.ts
@Injectable()
export class OrdersService {
  constructor(
    private readonly orders: OrdersRepository,   // O'Z repository (ruxsat ✓)
    private readonly users: UsersService,        // boshqa modul: PUBLIC service ✓
  ) {}

  async create(dto: CreateOrderDto) {
    // Boshqa modul bazasiga TEGMAYMIZ — uning public service'i orqali so'raymiz
    const user = await this.users.findById(dto.userId);
    if (!user) throw new NotFoundException('Foydalanuvchi topilmadi');
    return this.orders.save({ userId: user.id, items: dto.items });  // faqat O'Z jadvali
  }
}
OrdersService UsersRepository /users jadvali — ✕ UsersService (public) ✓ users · o‘z DB sxemasi
Ichki repo/jadvalga tegish ✕ · faqat public service orqali ✓

3 · Ma’lumotni ajratish — sxema bo‘yicha

// prisma/schema.prisma — bitta baza, lekin har modul O'Z sxemasida
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
  schemas  = ["users", "orders"]      // mantiqiy ajratish
}
generator client { provider = "prisma-client-js"; previewFeatures = ["multiSchema"] }

model User  { id String @id @default(uuid())  name String   @@schema("users")  }
model Order { id String @id @default(uuid())  userId String @@schema("orders") }
// DIQQAT: Order.userId ga users jadvaliga FK QO'YILMAYDI —
// modul chegarasini hurmat qilamiz (kelajakda ajratish oson bo'lsin)

4 · Chegarani avtomatik tekshirish (pro)

Insonning intizomiga ishonma — vositaga ishon. eslint-plugin-boundaries yoki dependency-cruiser bilan qoidani CI’da majburlaysan: masalan “orders moduli users modulining faqat index.tsidan import qila oladi, ichkarisidan emas”. Qoida buzilsa — build yiqiladi.

04
Modulli monolit · qism 4

NestJS implementatsiya

@Module · exports

Mikroservisdan farqli o‘laroq, bu yerda bitta NestJS ilova bor. “Xizmatlar” o‘rniga — bir jarayondagi modullar. Hech qanday transport, port yoki broker yo‘q.

Loyiha tuzilmasi

src/
├── main.ts
├── app.module.ts            # barcha modullarni birlashtiradi
├── shared/                  # umumiy: config, db, guard, util
└── modules/
    ├── users/
    │   ├── index.ts         # PUBLIC kontrakt
    │   ├── users.module.ts
    │   ├── users.controller.ts
    │   ├── users.service.ts        # public
    │   ├── users.repository.ts     # private
    │   └── user.types.ts
    ├── orders/
    │   ├── index.ts
    │   ├── orders.module.ts
    │   ├── orders.controller.ts
    │   ├── orders.service.ts
    │   └── orders.repository.ts
    └── notifications/
        ├── notifications.module.ts
        └── order.listener.ts       # event tinglaydi

Hammasini ulash

// app.module.ts — modullarni yig'amiz, BITTA ilova bo'lib ishga tushadi
@Module({
  imports: [
    SharedModule,
    UsersModule,
    OrdersModule,
    NotificationsModule,
  ],
})
export class AppModule {}

// main.ts — oddiy HTTP ilova (mikroservis EMAS, transport YO'Q)
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  await app.listen(3000);
}
E’tibor bering

Bu yerda OrdersService UsersServiceni to‘g‘ridan-to‘g‘ri chaqiradi — tarmoq emas, oddiy funksiya chaqirig‘i (nanosekundlar). users.findById() xato bersa — oddiy try/catch. Buyurtma + foydalanuvchi bitta ACID tranzaksiyada bo‘lishi mumkin. Mikroservisdagi Saga, timeout, retry — bu yerda kerak emas. Mana shu — soddalikning kuchi.

05
Chuqurlashtirilgan mavzu

Modullararo aloqa va ichki event

@nestjs/event-emitter
📨

G‘oya: 1-mavzudagi event-driven aloqa — lekin tarmoq va broker o‘rniga, bir jarayon ichida. Modullar bir-birini to‘g‘ridan chaqirish o‘rniga, hodisa tashlab, bo‘shroq bog‘lanishi mumkin.

Modullararo aloqaning ikki yo‘li bor, va to‘g‘ri tanlash muhim:

UsulQachonMisol
To‘g‘ridan chaqiruv
(public service)
Javob kerak bo‘lganda, sinxron, bitta tranzaksiyadausers.findById() — buyurtmadan oldin tekshirish
Ichki event
(EventEmitter2)
Javob kerak emas, bir necha modul reaksiya bildirgandaorder.created → email, analitika, sodiqlik

Ichki event — NestJS misoli

// modules/orders/orders.service.ts
constructor(
  private readonly orders: OrdersRepository,
  private readonly events: EventEmitter2,        // @nestjs/event-emitter
) {}

async create(dto: CreateOrderDto) {
  const order = await this.orders.save(dto);
  // Modul ichida HODISA tashlaymiz — Orders kim tinglashini bilmaydi
  this.events.emit('order.created', new OrderCreatedEvent(order.id, order.userId));
  return order;
}
// modules/notifications/order.listener.ts — BOSHQA modul, bo'sh bog'langan
@Injectable()
export class OrderListener {
  @OnEvent('order.created')
  async handle(event: OrderCreatedEvent) {
    // email yuborish — Orders moduli bundan butunlay bexabar
    await this.email.sendOrderConfirmation(event.userId, event.orderId);
  }
}
Strategik foyda

Bu shunchaki “toza kod” emas. order.created eventi — kelajakda Notifications modulini mikroservisga ajratishning tayyor nuqtasi: o‘sha kuni ichki EventEmitterni RabbitMQ emit()iga almashtirasan, qolgani o‘zgarmaydi. Modulli monolit — mikroservisga pog‘onali yo‘l.

06
Chuqurlashtirilgan mavzu

Mikroservisga pog‘onali ko‘chirish

ports & adapters · strangler fig

Modulli monolitning eng kuchli va’dasi: “keyin kerak bo‘lsa, oson ajrataman”. Chegaralar toza bo‘lgani uchun, bir modulni mikroservisga ko‘chirish — qayta yozish emas, almashtirish.

Sir — Dependency Inversion (15-mavzu: Clean Architecture)

Buning kaliti: OrdersService konkret UsersServicega emas, interfeysga (port) bog‘lansin. Keyin o‘sha portni har xil “adapter” bilan ta’minlaysan — lokal yoki masofaviy.

1 · Orders portga bog‘lanadi

// modules/orders/ports/users.port.ts — Orders FAQAT shu abstraksiyaga bog'liq
export interface UsersPort {
  findById(id: string): Promise<{ id: string; name: string } | null>;
}
export const USERS_PORT = Symbol('USERS_PORT');

// OrdersService endi konkret UsersService'ni emas, PORT'ni inject qiladi
@Injectable()
export class OrdersService {
  constructor(@Inject(USERS_PORT) private readonly users: UsersPort) {}
  // ... mantiq o'zgarmaydi
}

2 · Bugun — lokal adapter (in-process)

// HOZIR (monolit): lokal adapter — to'g'ridan UsersService'ni chaqiradi
@Injectable()
export class LocalUsersAdapter implements UsersPort {
  constructor(private readonly users: UsersService) {}
  findById(id: string) { return this.users.findById(id); }
}
// orders.module.ts:
providers: [
  OrdersService,
  { provide: USERS_PORT, useClass: LocalUsersAdapter },   // <-- shu ulanadi
]

3 · Ertaga — masofaviy adapter (mikroservis)

// KEYIN (mikroservisga ajratilganda): faqat ADAPTER almashadi
@Injectable()
export class RemoteUsersAdapter implements UsersPort {
  constructor(@Inject('USERS_SERVICE') private readonly client: ClientProxy) {}
  findById(id: string) {
    return firstValueFrom(this.client.send({ cmd: 'get_user' }, id));  // endi tarmoq
  }
}
// orders.module.ts da bitta qator o'zgaradi:
{ provide: USERS_PORT, useClass: RemoteUsersAdapter }
// OrdersService — BIR QATOR HAM o'zgarmaydi ✓
OrdersService USERS_PORT adapter (almashtiriladi) Local → service Remote → ClientProxy Users (lokal yoki uzoq)
Faqat adapter almashadi — biznes mantiq tegilmaydi (Strangler Fig usuli)
07
Qaror

Monolit vs Mikroservis — qaysi birini tanlash

Endi ikkalasini yonma-yon qo‘yamiz. Bu jadval — qaror qabul qilish uchun amaliy xarita:

MezonModulli monolitMikroservis
DeployBitta birlik — soddaHar xizmat alohida
MurakkablikPastYuqori (tarmoq, DevOps)
TranzaksiyaOson — ACID, bitta bazaQiyin — Saga, kompensatsiya
KechikishFunksiya chaqirig‘i (ns)Tarmoq (ms)
MasshtablashButun ilova birgaHar qism alohida
Nosozlik izolyatsiyasiUmumiy jarayonIzolyatsiyalangan
JamoaKichik–o‘rta, bitta kod bazaKo‘p mustaqil jamoa
TexnologiyaBitta stackHar xizmat o‘ziniki
Eng mosHolatlarning ~90%i — shu yerdan boshlangIsbotlangan masshtab/jamoa ehtiyoji
Eng qimmat xato — “Distributed Monolith”

Mikroservisga erta o‘tib, lekin xizmatlarni bir-biriga mahkam bog‘lab qo‘yish (umumiy baza, sinxron zanjirlar). Natija: mikroservis murakkabligi + monolit bog‘liqligi, ikkalasining yomon tomoni birga. Modulli monolit aynan shundan saqlaydi.

Conway qonuni

Tizim arxitekturasi jamoa tuzilishini aks ettiradi. 3 kishilik jamoa 12 ta mikroservisni eplay olmaydi. Avval jamoangga qara, keyin arxitektura tanla. Mikroservis — texnik yechim emas, ko‘pincha tashkiliy yechim (ko‘p jamoani ajratish uchun).

Amaliy qoida

Yangi loyiha? Modulli monolitdan boshla. Modullarni shu darsdagidek toza chegaralar bilan yoz. Qachonki aniq bir modul (a) mustaqil masshtab talab qilsa, (b) alohida jamoa egalik qilsa, (c) boshqa texnologiya talab qilsa — o‘shanda va faqat o‘shani mikroservisga ajrat. Hammasini emas.

08
Yakuniy

Eslab qolish kerak bo‘lgan 5 jumla

  • Modulli monolit = bitta deploy (monolit soddaligi) + aniq chegaralar (mikroservis tartibi).
  • Har modul tashqariga faqat bitta public service ochadi; repo/entity/jadval — yashirin. Chegarani kod va vosita majburlaydi, intizom emas.
  • Modullararo: javob kerak bo‘lsa public service chaqiruvi, kerak bo‘lmasa ichki event (EventEmitter2).
  • Toza chegara = mikroservisga pog‘onali yo‘l: portga bog‘lan, keyin lokal adapterni masofaviysiga almashtir — biznes mantiq tegilmaydi.
  • ~90% loyiha shu yerdan boshlashi kerak. Mikroservis — tashkiliy ehtiyoj paydo bo‘lganda, bittalab ajratib boriladi. “Distributed monolith”dan qoch.

Keyingi mavzu — biz bir necha bor tegib o‘tgan tushuncha, endi to‘liq ochamiz:

“3-mavzu: Event-Driven Architecture” — Kafka/RabbitMQ, pub/sub, event tartibi va NestJS implementatsiyasi bilan.