IlmHamroh
JavaScript Full-stack/30-qism. NestJS48/65-dars16 daqiqa
Mundarija (26)

Modular monolit va hexagonal arxitektura NestJS'da: chegaralar, portlar va adapterlar

Qisqacha: Modular monolit — bitta jarayon va bitta deploy, lekin ichi aniq chegarali modullarga bo'lingan: har modul faqat o'z public API'si orqali gaplashadi va boshqa modulning ichki fayllariga tegmaydi. Hexagonal arxitektura (ports and adapters) — biznes mantiq markazda. Tashqi dunyo (baza, HTTP, email) unga port (interfeys) orqali ulanadi. Har tashqi texnologiya esa almashtiriladigan adapter. vazifalar-api da bu allaqachon bor: VazifaOmbori — port, beshta ombor — adapter, domen fayllarida birorta @nestjs importi yo'q. Bu dars — kod yozmasdan, loyihani shu ko'z bilan o'qish va chegaralarni o'lchash.

Bu darsda

  • Modular monolit mikroservis va "katta loy to'pi"dan nimasi bilan farq qilishini tushuntirasiz.
  • Nest modulining exports i public API ekanini va uning chegaralarini bilasiz.
  • Hexagonal arxitekturani — port, adapter va freymvorksiz domenni — vazifalar-api misolida ko'rsatasiz.
  • Loyihaning haqiqiy import grafini o'qib, chegara buzilishlarini topasiz.
  • Chegara tekshiruvchisini (modullar linteri) o'zingiz yozasiz.

Oldin bilishingiz kerak: Modullar, Repository naqshi va qatlamlar, CQRS moduli, Mikroservis asoslari NestJS'da.

Oldingi besh darsda GraphQL, mikroservislar va CQRS'ni sinadik — hammasi "kanonga kirmadi". Tabiiy savol: unda vazifalar-api qanday arxitekturada? Javob — modular monolit, ichida hexagonal yadro bilan. Bu dars — hujjat darsi: yangi kod yo'q, mavjud kodni yangi ko'z bilan o'qiymiz.

1. Nega bu kerak?

Monolitning ikki xil o'limi bor.

Birinchisi — katta loy to'pi (big ball of mud): har fayl har faylni import qiladi, hamma narsa hamma narsaga bog'liq. Bitta joyni o'zgartirsangiz, kutilmagan uchta joy buziladi. Yangi dasturchi bir oy davomida "bu kod qayerda?" deb so'raydi.

Ikkinchisi — shunga qochib, vaqtidan oldin mikroservislarga bo'linish. Mikroservis asoslari darsida narxini o'lchadik: har chaqiruv tarmoqqa chiqadi, tranzaksiya taqsimlanadi, deploy ko'payadi.

O'rtadagi yo'l — modular monolit: kod bitta jarayonda, lekin modullar orasida mikroservislardagidek qat'iy chegara. Modullar bir-birini metod bilan chaqiradi (tez, tranzaksiyali), lekin faqat ruxsat etilgan "eshik" orqali. Kerak bo'lsa, keyinroq bitta modulni mikroservisga ajratish oson — chegara allaqachon bor.

O'xshatish: ko'p qavatli uy. Mikroservislar — alohida uylar mahallasi: har birida o'z isitish, o'z suv. Katta loy to'pi — devorlari yo'q bitta zal. Modular monolit — bitta bino, lekin har xonadonning o'z eshigi va qulfi bor; quvurlar umumiy, ammo qo'shnining oshxonasiga devor teshib kirilmaydi.

2. Modul chegarasi Nest'da

2.1 exports — public API

Modullar darsida ko'rdik: Nest modulidagi provider sukut bo'yicha yopiq — faqat exports dagisi boshqa modullarga ko'rinadi. Bu chegaraning birinchi qatlami:

ts
@Module({
  controllers: [VazifalarController, TeglarController],
  providers: [VazifalarService, WebhookTinglovchi],
  // boshqa modul (masalan eksport) xizmatdan foydalanmoqchi
  // bo'lsa — eksport qilinadi
  exports: [VazifalarService],
})
export class VazifalarModule {}

VazifalarModule ning public API'si — VazifalarService. WebhookTinglovchi — ichki, boshqa modul uni ololmaydi. Buni DI konteyner ish vaqtida qo'riqlaydi: exports siz provider'ni so'rasangiz, Nest can't resolve dependencies xatosi.

2.2 Nest himoya qilmaydigan narsa

Lekin Nest faqat DI ni qo'riqlaydi, import ni emas. Istalgan fayl import { ... } from "../vazifalar/sxema.ts" qila oladi — TypeScript ham, Nest ham to'xtatmaydi. Shuning uchun haqiqiy chegara ikki qatlamdan iborat:

Qatlam Kim qo'riqlaydi Nima
DI (provider'lar) Nest, ish vaqtida faqat exports dagi provider
Fayl importlari hech kim — yoki linter boshqa modulning ichki fayllari

Ikkinchi qatlamni ko'pincha unutishadi. Natijada modullar "DI bo'yicha" ajratilgan, lekin fayllar bo'yicha bir-biriga chirmashib ketgan bo'ladi.

2.3 Bounded context

Modulni qanday chegaralash kerak? Texnik qatlam bo'yicha (controllers/, services/) emas — biznes mavzusi bo'yicha. DDD bu mavzuni bounded context (chegaralangan kontekst) deydi: o'z tili, o'z qoidalari bor soha bo'lagi. "Vazifa" so'zi vazifalar modulida bitta ma'noga ega; to'lov modulida esa "to'lov vazifasi" butunlay boshqa narsa bo'lishi mumkin. Har kontekst — bitta modul. Mavzuning nazariyasi arxitektura qismida, DDD strategik darsida.

Modular monolitda ko'pincha yana bitta qoida qo'shiladi: har modulning o'z jadvallari. Boshqa modul ularni to'g'ridan SELECT qilmaydi — faqat public API orqali so'raydi. Bu keyinchalik modulni alohida bazaga ko'chirishni mumkin qiladi. vazifalar-api da hozircha bitta asosiy jadval bor, shuning uchun bu qoida hali sinalmagan.

Tekshirib ko'ring: EksportService VazifalarService.royxat() ni chaqirish o'rniga VAZIFA_OMBORI tokenini to'g'ridan oladi. Bu chegara buzilishimi?

Javob

Qisman. VAZIFA_OMBORI — OmborModule ning eksporti va u @Global: Nest bunga ruxsat beradi. Lekin bu bilan eksport vazifalar xizmatining qoidalarini (masalan, keyinchalik qo'shiladigan "faqat o'z vazifalari" filtri) chetlab o'tadi. Auth qo'shilganda aynan shu yo'l bilan ma'lumot sizishi mumkin edi — kanon auth'ni ko'chirganda eksport ishlarini egasiga cheklashga majbur bo'lgani shundan. Qat'iy modular monolitda eksport VazifalarService ning public metodini chaqirgan bo'lardi.

3. Hexagonal arxitektura

3.1 Port va adapter

Hexagonal arxitektura (ports and adapters, Alistair Cockburn, 2005) — ilovani markaz va chetga ajratib ko'rish usuli. Uch qismi bor:

  • Markaz (domen) — biznes qoidalari: "1 000 tadan ko'p vazifa bo'lmaydi", "versiya mos kelmasa — 412". Unda baza, HTTP, freymvork haqida hech narsa yo'q.
  • Port — markaz tashqi dunyodan nima kutishini aytadigan interfeys: "menga vazifalarni saqlaydigan va o'qiydigan narsa kerak".
  • Adapter — portning aniq bajarilishi: Postgres bilan, Prisma bilan, xotirada.

vazifalar-api da port — VazifaOmbori interfeysi (25-qismdan beri, Repository naqshi):

ts
// ombor.ts — repository interfeysi: xizmat saqlash qanday
// ishlashini bilmaydi. Hamma metod async — 26-qismda Postgres (pg)
// bilan almashganda chaqiruvchi kod o'zgarmaydi
import type {
  Vazifa, VazifaOzgarishi, YangiVazifa,
} from "./vazifa.ts";

Adapterlar — beshta: xotira-ombor.ts, fayl-ombor.ts, pg-ombor.ts, prisma-ombor.ts, kesh-ombor.ts (kesh boshqa omborni o'raydi). Qaysi biri ishlashini SAQLASH sozlamasi hal qiladi, OmborModule.forRootAsync() uni VAZIFA_OMBORI tokeni ostida beradi (Dynamic modullar). Xizmat esa faqat tokenni biladi:

ts
constructor(
  @Inject(VAZIFA_OMBORI) private readonly ombor: VazifaOmbori,
  // ...
) {}
flowchart LR
  H["HTTP controller"] --> S["VazifalarService"]
  T["testlar"] --> S
  S --> P{{"port: VazifaOmbori"}}
  P --> X["xotira"]
  P --> F["fayl"]
  P --> PG["pg"]
  P --> PR["Prisma"]
  P --> K["kesh + ichki ombor"]

Chapda — markazni chaqiradiganlar (kiruvchi adapterlar: HTTP, testlar). O'ngda — markaz chaqiradiganlar (chiquvchi adapterlar: omborlar). Markaz ikki tomonni ham bilmaydi.

3.2 Bu nima berdi — 26–27-qismlardan dalil

Bu nazariya emas, loyihada isbotlangan:

  • 26-qismda saqlash xotiradan Postgres'ga o'tdi — xizmat va controller o'zgarmadi.
  • 27-qismda Prisma va Redis kesh qo'shildi — yana o'zgarmadi.
  • 30-qismda butun HTTP qatlami Express'dan Nest'ga ko'chdi — vazifa.ts, royxat-mantiq.ts va beshta ombor o'zgarmadi. 25–27-qismlarning ombor shartnoma testlari Nest'da ham o'zgarishsiz o'tdi.
  • Health check'lar darsidagi OmborIndikatori ham portga ulangan: u omborning qaysi turi ekanini bilmaydi.

Oxirgi ko'chish — eng katta dalil. Freymvork almashdi, markaz turib qoldi.

3.3 Domen freymvorksizmi?

Tekshirdik: vazifalar/ va umumiy/ papkalaridagi har faylda @nestjs yoki express importi bormi (biz sanab chiqdik, kanonning shu bosqichida):

Fayllar @nestjs/express importi
vazifa.ts, royxat-mantiq.ts, ombor.ts, sorov.ts, sxema.ts 0
5 ta ombor adapteri 0
csv.ts, kursor.ts, sana.ts, xatolar.ts, webhook.ts 0
*.controller.ts, *.service.ts, *.module.ts, guard, pipe, filter 1–5

Domen va adapterlar — toza TypeScript. Nest faqat chetdagi "yelim" qatlamida: controller, modul, xizmat klassining dekoratori. Bu Nest loyihalari uchun yaxshi belgi: freymvork ichkariga sizib kirmagan.

VazifalarService ning o'zida esa ikki Nest importi bor (@Injectable, EventEmitter2). Qat'iy hexagonal maktab uni ham toza klass qilib, Nest'ga alohida provider orqali ulashni talab qiladi. Kanon bu darajaga bormagan — o'lchovli qaror: xizmat klassi kichik, dekorator esa faqat metama'lumot.

4. vazifalar-api import grafi — o'lchov

4.1 Skaner

Chegaralarni his bilan emas, o'lchab tekshirish kerak. Biz kichik Node skript yozdik: src/ dagi har .ts fayldan nisbiy importlarni oladi va papkalar (modullar) orasidagi bog'lanishni yig'adi. Skriptning yuragi:

js
for (const f of fayllar) {
  const s = readFileSync(f, "utf8");
  for (const m of s.matchAll(/from "(\.[^"]+)"/g)) {
    const t = resolve(dirname(f), m[1]);
    const a = papka(f);
    const b = papka(t);
    if (a !== b) qosh(`${a} → ${b}`, t); // boshqa modulga
  }
}

4.2 Natija

Kanonning SSE va salomatlik qo'shilgan bosqichidagi src/ ga ishga tushirdik (biz ishga tushirdik, Node 24.21.0; (ildiz) — src/ ning o'zidagi fayllar, ro'yxatni qisqartirdik). Har qatorni shunday o'qing: "chapdagi modul → o'ngdagi modulning shu fayllarini import qiladi". Ikki tomonlama strelka bor-yo'qligiga qarang:

text
eksport → umumiy: umumiy/csv.ts, umumiy/dekoratorlar.ts, umumiy/log.ts, umumiy/xatolar.ts
eksport → vazifalar: vazifalar/ombor.ts, vazifalar/sxema.ts, vazifalar/vazifa.ts
salomatlik → vazifalar: vazifalar/ombor.ts
umumiy → vazifalar: vazifalar/ombor.ts, vazifalar/vazifa.ts
vazifalar → umumiy: umumiy/dekoratorlar.ts, umumiy/idempotentlik.middleware.ts, umumiy/json-tana.guard.ts, umumiy/kursor.ts, umumiy/log.ts, umumiy/sana.ts, umumiy/vazifa-sarlavhalari.interceptor.ts, umumiy/webhook.ts, umumiy/xatolar.ts
(ildiz) → vazifalar: vazifalar/fayl-ombor.ts, vazifalar/kesh-ombor.ts, vazifalar/ombor.ts, vazifalar/pg-ombor.ts, vazifalar/prisma-ombor.ts, vazifalar/vazifalar.module.ts, vazifalar/xotira-ombor.ts

4.3 Halol tahlil

Grafni qatorma-qator o'qiymiz:

  1. salomatlik → vazifalar/ombor.ts — faqat port. Ideal: salomatlik moduli omborning ichini bilmaydi.
  2. eksport → vazifalar/sxema.ts, vazifa.ts — eksport vazifalar modulining ichki fayllarini oladi: tur va Zod sxemasi. Bu yumshoq chegara. Toza yechim — umumiy turlarni modulning ochiq fayliga chiqarish (masalan vazifalar/index.ts yoki alohida kontrakt.ts).
  3. umumiy ↔ vazifalar — ikki tomonlama. vazifalar umumiy yordamchilarni oladi (bu normal). Lekin umumiy ham vazifalar dan oladi: kursor.ts, shartli.ts, webhook.ts, sarlavhalar interceptor'i Vazifa va KursorQiymati turlarini import qiladi. "Umumiy" papka aslida vazifalarga xos kodni ham saqlayapti. Modul darajasida bu — aylanma bog'liqlik.
  4. (ildiz) → vazifalar/pg-ombor.ts va boshqa adapterlar — ombor-yarat.ts hamma adapterni biladi. Bu buzilish emas: bu kompozitsiya ildizi (composition root) — ilova yig'iladigan yagona joy. Adapterlarni kimdir tanlashi kerak; hexagonal arxitektura bu joyni ataylab chetga qo'yadi.

Uchinchi punkt xavflimi? Tekshirdik: umumiy dan vazifalar ga hamma importlar import type — faqat tur. TypeScript ularni JS'ga o'girganda o'chirib yuboradi. Ish vaqtida aylanma yo'q. Bu muhim: Circular dependency darsida kanon ko'rsatgan — ESM'da ish vaqtidagi aylanma import Vitest'da o'tib, build'dan keyin yiqiladi. Turlar halqasi esa zararsiz, lekin "kod hidi": umumiy — haqiqatan umumiymi?

Tekshirib ko'ring: Kimdir umumiy/kursor.ts ga import { MAKS_VAZIFA } from "../vazifalar/vazifalar.service.ts" (qiymat, tur emas) qo'shdi. Nima o'zgaradi?

Javob

Turlar halqasi haqiqiy, ish vaqtidagi halqaga aylanadi: vazifalar.service.ts → (umumiy/... orqali) → kursor.ts → vazifalar.service.ts. ESM'da qaysi modul birinchi yuklanishiga qarab, MAKS_VAZIFA hali undefined (aniqrog'i TDZ xatosi) bo'lishi mumkin. Kanon tajribasida aynan shunday holat testda o'tib, build'dan keyin yiqilgan. Shuning uchun chegara tekshiruvchisi qiymat importlarini qat'iyroq tekshirishi kerak (3-mashq).

5. Modullararo aloqa: hodisalar

Chegaralangan modullar qanday gaplashadi? Ikki yo'l:

Usul Qachon Loyihada
Public API chaqiruvi javob kerak, darhol eksport → ombor porti
Domen hodisasi "bo'ldi" — kim eshitsa vazifa.bajarildi → webhook, SSE

Hodisalar darsida webhook xizmatdan chiqib, tinglovchiga o'tdi. Modular monolit tilida: vazifalar moduli bildirishnomalar modulini bilmaydi. 29-qism integratsiyalari (email, Telegram) ham shu naqsh bilan ulanadi. Keyinchalik bildirishnomalarni alohida xizmatga ajratish kerak bo'lsa, faqat tinglovchi o'zgaradi: in-process @OnEvent o'rniga broker (Transport'lar chuqur) yoki outbox.

Bu modular monolitning asosiy va'dasi: mikroservisga o'tish — chegara bo'ylab kesish, kodni qayta yozish emas.

6. JS modelida chegara tekshiruvchisi

Real loyihalarda chegaralarni avtomatik tekshirish uchun vositalar bor: eslint-plugin-boundaries, dependency-cruiser, Nx'ning module boundaries qoidasi. Biz ularni sinamadik. G'oyasi esa sodda — o'zimiz yozamiz. Har modulning ochiq fayllari ro'yxati bor; boshqa moduldan faqat ularni import qilish mumkin. Ma'lumot — yuqoridagi skaner natijasidan. Blok brauzerda ishlaydi:

js
// Chegara qoidasi: boshqa modulga faqat uning ochiq fayllari orqali
const ochiq = {
  vazifalar: [
    "vazifalar.module.ts", "vazifalar.service.ts", "ombor.ts",
  ],
  eksport: ["eksport.module.ts"],
  salomatlik: ["salomatlik.module.ts"],
};

// loyiha skaneri topgan importlarning bir qismi
const importlar = [
  ["eksport", "vazifalar/ombor.ts"],
  ["eksport", "vazifalar/sxema.ts"],
  ["eksport", "vazifalar/vazifa.ts"],
  ["salomatlik", "vazifalar/ombor.ts"],
  ["(ildiz)", "vazifalar/vazifalar.module.ts"],
  ["(ildiz)", "vazifalar/pg-ombor.ts"],
];

function tekshir(royxat) {
  const buzilish = [];
  for (const [kim, fayl] of royxat) {
    const [modul, nom] = fayl.split("/");
    if (kim === modul || !(modul in ochiq)) continue;
    if (!ochiq[modul].includes(nom)) {
      buzilish.push(`${kim} → ${fayl}`);
    }
  }
  return buzilish;
}

const b = tekshir(importlar);
const n = importlar.length;
console.log(`${n} ta importdan ${b.length} tasi chegarani buzadi:`);
for (const x of b) console.log(`  ${x}`);

Konsolda:

text
6 ta importdan 3 tasi chegarani buzadi:
  eksport → vazifalar/sxema.ts
  eksport → vazifalar/vazifa.ts
  (ildiz) → vazifalar/pg-ombor.ts

Birinchi ikkitasi — «Halol tahlil» bo'limidagi topilmaning o'zi, endi avtomatik. Uchinchisi — kompozitsiya ildizi: haqiqiy linterda (ildiz) ga, aniqrog'i ombor-yarat.ts ga istisno beriladi. Linter "qoida + istisnolar ro'yxati" bilan ishlaydi; istisnolar ro'yxati qisqa bo'lsa — arxitektura sog'lom. Soddalashtirilgan joyi: model yo'llarni tahlil qilmaydi, ma'lumotni tayyor oladi; haqiqiy vosita fayl tizimini o'zi aylanadi.

7. Qachon qaysi arxitektura

Loyiha Tavsiya
Kichik, 1–3 dasturchi oddiy Nest modullari, qatlamlar (hozirgi vazifalar-api)
O'rta, bir necha jamoa, bitta mahsulot modular monolit + chegara linteri
Murakkab domen, ko'p qoida + hexagonal yadro, kerak bo'lsa CQRS
Mustaqil jamoalar, alohida yuk modulni mikroservisga ajratish

Har qadam oldingisidan keyin, zarurat bo'lganda. Teskari tartibda — "boshidan mikroservis va CQRS" — eng qimmat xato.

8. Ko'p uchraydigan xatolar

Xato Nima bo'ladi Tuzatish
Modullar texnik qatlam bo'yicha (services/, repos/) bitta o'zgarish hamma papkaga tegadi biznes mavzusi bo'yicha
"exports bor — chegara bor" fayl importlari chirmashadi linter bilan qo'riqlash
umumiy/ ga hamma narsa yashirin aylanma bog'liqlik domenga xos kod — o'z modulida
Domenda @nestjs va express freymvork almashsa domen ham o'zgaradi dekoratorlar chetda
Har modulga @Global DI chegarasi yo'qoladi faqat haqiqatan umumiy provider'ga
Boshqa modul jadvaliga to'g'ridan SELECT modulni ajratib bo'lmaydi public API orqali

9. Mashqlar

Mashqlar kurs/mashqlar/30/48-modular-monolit/ da.

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

  • Hexagonal arxitekturada markaz tashqaridan nima kutishini aytadigan interfeys —
  • Portning aniq bajarilishi (masalan Postgres bilan) —
  • Nest modulida boshqa modullarga ochiq provider'lar ro'yxati —
  • Hamma adapterlarni biladigan va ilovani yig'adigan yagona joy — kompozitsiya
  • TypeScript JS'ga o'girganda o'chirib yuboradigan import turi — import
Yechim
  • port — VazifaOmbori interfeysi va VAZIFA_OMBORI tokeni.
  • adapter — pg-ombor.ts, prisma-ombor.ts va boshqalar.
  • exports — VazifalarModule da faqat VazifalarService.
  • ildizi — ombor-yarat.ts; chegara qoidasidan istisno.
  • type — ish vaqtida aylanma bog'liqlik yaratmaydi.

2-mashq (o'rta): modullar halqasini topish

Modullar orasidagi bog'lanishlar (qirralar) berilgan: skaner natijasidan eksport → vazifalar, eksport → umumiy, salomatlik → vazifalar, umumiy → vazifalar, vazifalar → umumiy. Grafdagi har aylanma bog'liqlikni bir marta chiqaradigan funksiya yozing. Ishora: qo'shnilar Map i; chuqurlik bo'yicha yurish (rekursiya) — joriy yo'lda tugun takrorlansa, halqa topildi; bir halqani ikki marta chiqarmaslik uchun tugunlarini saralab Set ga kalit qiling.

Yechim
js
// Modullar grafida aylanma bog'liqlikni topish
const qirralar = [
  ["eksport", "vazifalar"],
  ["eksport", "umumiy"],
  ["salomatlik", "vazifalar"],
  ["umumiy", "vazifalar"],
  ["vazifalar", "umumiy"],
];

function aylanmalar(qirralar) {
  const qoshni = new Map();
  for (const [a, b] of qirralar) {
    qoshni.set(a, [...(qoshni.get(a) ?? []), b]);
  }
  const topildi = new Set();
  function yur(tugun, yol) {
    if (yol.includes(tugun)) {
      const halqa = yol.slice(yol.indexOf(tugun));
      const kalit = halqa.toSorted().join(",");
      if (!topildi.has(kalit)) {
        topildi.add(kalit);
        console.log([...halqa, tugun].join(" → "));
      }
      return;
    }
    for (const k of qoshni.get(tugun) ?? []) yur(k, [...yol, tugun]);
  }
  for (const t of qoshni.keys()) yur(t, []);
  return topildi.size;
}

console.log(`aylanmalar: ${aylanmalar(qirralar)}`);

Konsolda:

text
vazifalar → umumiy → vazifalar
aylanmalar: 1

Bitta halqa — darsdagi tahlildagi umumiy ↔ vazifalar. eksport va salomatlik halqaga kirmaydi: ulardan faqat chiquvchi qirra bor. dependency-cruiser kabi vositalar aynan shu hisobni katta loyihada bajaradi va CI'da "yangi halqa paydo bo'ldi" deb to'xtatadi.

3-mashq (qiyin): faqat ish vaqtidagi halqalar

Importlarning turi bor: type (faqat tur, o'chib ketadi) yoki qiymat. Halqa bormi — ikki marta tekshiring: hamma importlar bo'yicha va faqat qiymat importlari bo'yicha. Ma'lumot: umumiy → vazifalar (type), vazifalar → umumiy (qiymat), eksport → vazifalar (qiymat). Soddalik uchun faqat ikki modulli halqani (A → B va B → A) qidiring. Ishora: Set da "A>B" satrlarini saqlang va teskarisini qidiring; filter va some.

Yechim
js
// import type ish vaqtida yo'qoladi —
// faqat qiymat importlari halqa yasaydi
const importlar = [
  { kim: "umumiy", kimdan: "vazifalar", tur: "type" },
  { kim: "vazifalar", kimdan: "umumiy", tur: "qiymat" },
  { kim: "eksport", kimdan: "vazifalar", tur: "qiymat" },
];

function halqaBormi(royxat) {
  const juft = new Set(royxat.map((i) => `${i.kim}>${i.kimdan}`));
  return royxat.some((i) => juft.has(`${i.kimdan}>${i.kim}`));
}

console.log("hammasi:", halqaBormi(importlar));
const ishVaqti = importlar.filter((i) => i.tur === "qiymat");
console.log("ish vaqtida:", halqaBormi(ishVaqti));

Konsolda:

text
hammasi: true
ish vaqtida: false

Kanondagi holat aynan shu: tur darajasida halqa bor, ish vaqtida yo'q. Ikki xil qoida qo'yish mumkin: ish vaqtidagi halqa — xato (build'dan keyin yiqiladi), turlar halqasi — ogohlantirish (arxitektura hidi). eslint-plugin-import ning no-cycle qoidasida ham turlarni hisobga olmaslik sozlamasi bor (hujjat bo'yicha).

10. Loyiha qadami: hujjat, kod yo'q

Bu darsda vazifalar-api kodi o'zgarmaydi — kanonda ham bu dars hujjat sifatida qayd etilgan. O'z loyihangizda qiling:

  1. Skaner bilan import grafini oling va o'zingiznikini darsdagi natija bilan solishtiring.
  2. Natijani docs/ papkasidagi qisqa arxitektura izohiga yozing: modullar, har birining public API'si, kompozitsiya ildizi, ma'lum yumshoq joylar (eksport → vazifalar/sxema.ts, umumiy ↔ vazifalar turlari).
  3. Ixtiyoriy: eksport ishlatadigan turlarni vazifalar ning ochiq fayliga chiqarish rejasini yozing. Hozir qilmang — keyingi darslarda testlar va 28–29-qism kodi shu fayllarga tayanadi; o'zgarish alohida, testlangan qadam bo'lishi kerak.

Commit kerak bo'lsa — faqat hujjat uchun: docs: arxitektura — modullar va chegaralar.

11. Real ishda

  • Qayerda uchraydi: Shopify (Ruby'dagi mashhur modular monolit), ko'plab fintech va e-commerce jamoalari mikroservisdan qaytib, modular monolitga o'tgani haqida yozgan. Nest'da modullar tizimi buning uchun tabiiy asos.
  • Hexagonal — test qulayligi uchun ham tanlanadi: domenni xotiradagi adapter bilan millisekundlarda sinash mumkin (vazifalar-api testlari aynan shunday — XotiraOmbori).
  • Intervyu savollari: "Modular monolit nima va mikroservisdan nimasi bilan farq qiladi?", "Ports and adapters nima?", "Modul chegarasini qanday qo'riqlaysiz?", "Aylanma bog'liqlik nega xavfli?".
  • Kod review'da: "bu import boshqa modulning ichiga kirmayaptimi?" — linter bo'lmasa, odam tekshiradi.

Xulosa

  • Modular monolit — bitta deploy, lekin modullar orasida qat'iy chegara; mikroservisga o'tish keyin — chegara bo'ylab kesish.
  • Nest exports — DI chegarasi; fayl importlarini Nest qo'riqlamaydi — linter kerak.
  • Hexagonal: domen markazda, port — interfeys, adapter — texnologiya; vazifalar-api da VazifaOmbori + 5 adapter, domen fayllarida @nestjs yo'q.
  • Freymvork almashganda (Express → Nest) domen va omborlar o'zgarmadi — arxitekturaning eng kuchli dalili.
  • O'lchov: eksport → vazifalar ichki turlari va umumiy ↔ vazifalar turlar halqasi — yumshoq joylar; ish vaqtida halqa yo'q.
  • Modullararo aloqa — public API yoki domen hodisalari.

Keyingi dars: NestJS unit testlari — Test.createTestingModule bilan xizmatni soxta ombor bilan sinash: hexagonal arxitekturaning test foydasini amalda ko'ramiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Modular monolit va hexagonal arxitektura NestJS'da: chegaralar, portlar va adapterlar — IlmHamroh