Mundarija (26)
- Bu darsda
- 1. Nega bu kerak?
- 2. Modul chegarasi Nest'da
- 2.1 exports — public API
- 2.2 Nest himoya qilmaydigan narsa
- 2.3 Bounded context
- 3. Hexagonal arxitektura
- 3.1 Port va adapter
- 3.2 Bu nima berdi — 26–27-qismlardan dalil
- 3.3 Domen freymvorksizmi?
- 4. vazifalar-api import grafi — o'lchov
- 4.1 Skaner
- 4.2 Natija
- 4.3 Halol tahlil
- 5. Modullararo aloqa: hodisalar
- 6. JS modelida chegara tekshiruvchisi
- 7. Qachon qaysi arxitektura
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): modullar halqasini topish
- 3-mashq (qiyin): faqat ish vaqtidagi halqalar
- 10. Loyiha qadami: hujjat, kod yo'q
- 11. Real ishda
- Xulosa
- Manbalar
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-apida bu allaqachon bor:VazifaOmbori— port, beshta ombor — adapter, domen fayllarida birorta@nestjsimporti 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
exportsi public API ekanini va uning chegaralarini bilasiz. - Hexagonal arxitekturani — port, adapter va freymvorksiz domenni —
vazifalar-apimisolida 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:
@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:
EksportServiceVazifalarService.royxat()ni chaqirish o'rnigaVAZIFA_OMBORItokenini 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):
// 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:
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.tsva beshta ombor o'zgarmadi. 25–27-qismlarning ombor shartnoma testlari Nest'da ham o'zgarishsiz o'tdi. - Health check'lar darsidagi
OmborIndikatoriham 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:
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:
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.ts4.3 Halol tahlil
Grafni qatorma-qator o'qiymiz:
salomatlik → vazifalar/ombor.ts— faqat port. Ideal: salomatlik moduli omborning ichini bilmaydi.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 (masalanvazifalar/index.tsyoki alohidakontrakt.ts).umumiy ↔ vazifalar— ikki tomonlama.vazifalarumumiy yordamchilarni oladi (bu normal). Lekinumumiyhamvazifalardan oladi:kursor.ts,shartli.ts,webhook.ts, sarlavhalar interceptor'iVazifavaKursorQiymatiturlarini import qiladi. "Umumiy" papka aslida vazifalarga xos kodni ham saqlayapti. Modul darajasida bu — aylanma bog'liqlik.(ildiz) → vazifalar/pg-ombor.tsva boshqa adapterlar —ombor-yarat.tshamma 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.tsgaimport { 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:
// 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:
6 ta importdan 3 tasi chegarani buzadi:
eksport → vazifalar/sxema.ts
eksport → vazifalar/vazifa.ts
(ildiz) → vazifalar/pg-ombor.tsBirinchi 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 —
VazifaOmboriinterfeysi vaVAZIFA_OMBORItokeni. - adapter —
pg-ombor.ts,prisma-ombor.tsva boshqalar. - exports —
VazifalarModuleda faqatVazifalarService. - 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
// 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:
vazifalar → umumiy → vazifalar
aylanmalar: 1Bitta 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
// 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:
hammasi: true
ish vaqtida: falseKanondagi 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:
- Skaner bilan import grafini oling va o'zingiznikini darsdagi natija bilan solishtiring.
- Natijani
docs/papkasidagi qisqa arxitektura izohiga yozing: modullar, har birining public API'si, kompozitsiya ildizi, ma'lum yumshoq joylar (eksport → vazifalar/sxema.ts,umumiy ↔ vazifalarturlari). - Ixtiyoriy:
eksportishlatadigan turlarnivazifalarning 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-apitestlari 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-apidaVazifaOmbori+ 5 adapter, domen fayllarida@nestjsyo'q. - Freymvork almashganda (Express → Nest) domen va omborlar o'zgarmadi — arxitekturaning eng kuchli dalili.
- O'lchov:
eksport → vazifalarichki turlari vaumumiy ↔ vazifalarturlar 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
- NestJS hujjati — Modules (encapsulation, exports, global modules): https://docs.nestjs.com/modules
- Alistair Cockburn, "Hexagonal Architecture" (2005): https://alistair.cockburn.us/hexagonal-architecture/
- Martin Fowler, "BoundedContext": https://martinfowler.com/bliki/BoundedContext.html
- Martin Fowler, "MonolithFirst": https://martinfowler.com/bliki/MonolithFirst.html
dependency-cruiser: https://github.com/sverweij/dependency-cruiser ;eslint-plugin-boundaries: https://github.com/javierbrea/eslint-plugin-boundaries- Kanon (IlmHamroh, 2026-10-28): modullar chegarasi,
VazifaOmboriporti va adapterlar — hujjat sifatida. Import grafi skaneri, freymvork importlarini sanash va chegara tahlili — biz ishga tushirdik (2026-11-02, Node 24.21.0, kanonning SSE va salomatlik bosqichidagisrc/).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!