Mundarija (25)
- Bu darsda
- 1. Nega bu kerak?
- 2. Uch scope
- 2.1 Jadval
- 2.2 @Inject(REQUEST)
- 2.3 TRANSIENT qachon foydali
- 3. Kanon tajribasi: kim necha marta yaratildi
- 3.1 Tajriba sharti
- 3.2 Natija
- 3.3 Narxi
- 4. JS modelida scope va yuqish
- 5. Express'da qanday bo'lardi va nima yashirin
- 5.1 Scope va lifecycle hook'lar
- 5.2 Scope'ni qanday topish mumkin
- 6. Durable provider'lar — qisqacha
- 7. Nega vazifalar-api da hamma provider DEFAULT
- 7.1 Joriy so'rov ma'lumotini olishning uch yo'li
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): scope'ni toping
- 2-mashq (o'rta): yuqishni hisoblang
- 3-mashq (qiyin): TRANSIENT — klass va nusxa
- 10. Real ishda
- Xulosa
- Manbalar
NestJS provider scope'lari: DEFAULT, REQUEST, TRANSIENT va scope yuqishi
Qisqacha: Scope — provider nusxasi qancha yashashi.
DEFAULT(sukut) — butun ilovaga bitta nusxa, ishga tushishda yaratiladi.REQUEST— har so'rovga yangi nusxa, so'rov tugagach tashlanadi.TRANSIENT— har iste'molchi klassga alohida nusxa. REQUEST scope yuqadi: unga tayangan har provider va controller ham har so'rovda qayta yaratiladi. Kanon o'lchovida bu taxminan 43 % sekinlashtirdi, shuning uchunvazifalar-apida hamma providerDEFAULT; so'rov konteksti AsyncLocalStorage bilan olinadi.
Bu darsda
- Uch scope'ni va har birining nusxasi qachon yaratilishini bilasiz.
- Scope yuqishini (bubbling) kanon tajribasining haqiqiy sanog'ida ko'rasiz.
@Inject(REQUEST)bilan joriy so'rovni olishni va uning narxini bilasiz.- TRANSIENT "har iste'molchiga" degani aslida nimani anglatishini tushuntirasiz.
- Nega
vazifalar-apida hamma provider singleton ekanini o'lchov bilan asoslaysiz.
Oldin bilishingiz kerak: Custom provider'lar, Provider'lar va service'lar, AsyncLocalStorage, Correlation ID va so'rov kuzatuvi.
Provider'lar darsida singleton'ning ikkinchi tomonini ko'rgandik: nusxa hamma so'rovlar uchun umumiy, unga so'rov holati yozilmaydi. Unda "har so'rovga o'z nusxasi" kerak bo'lsa-chi? Nest buning uchun scope'larni beradi. Bu dars — tajriba: kanon uchala scope'ni o'lchadi va loyihaga birortasini kiritmadi. Nega — dars oxirida.
1. Nega bu kerak?
Tasavvur qiling, har log qatoriga joriy foydalanuvchi ID'sini qo'shmoqchisiz. Singleton xizmatda buni maydonga yozish xato — ikki parallel so'rov bir-birining ID'sini ustidan yozadi. To'g'ri yo'llar ikkita:
- Xizmat har so'rovda yangidan yaratilsin va so'rovni constructor'da olsin. Bu — REQUEST scope.
- Xizmat bitta qolsin, joriy so'rov esa "yashirin kanal" orqali olinsin. Bu — 24-qismdagi AsyncLocalStorage;
vazifalar-apidajoriyLog(this.log)shunday ishlaydi.
Birinchi yo'l oddiy ko'rinadi. Lekin uning narxi bor va u kutilmagan joylarga "yuqadi". Shuni o'lchab ko'ramiz.
O'xshatish: mehmonxona. DEFAULT — bitta qabulxona, hamma mehmonga xizmat qiladi. REQUEST — har mehmonga o'z shaxsiy xizmatchisi; u mehmon ketgach ishdan bo'shaydi. TRANSIENT — har bo'limning (oshxona, kir yuvish) o'z yordamchisi. Shaxsiy xizmatchi qulay, lekin har mehmonga yangi odam yollash qimmat.
2. Uch scope
2.1 Jadval
| Scope | Nusxa | Qachon yaratiladi |
|---|---|---|
DEFAULT |
butun ilovaga bitta | ishga tushishda |
REQUEST |
har so'rovga bitta | so'rov kelganda; so'rov tugagach tashlanadi |
TRANSIENT |
har iste'molchi klassga bitta | iste'molchi yaratilganda |
Hech narsa yozmasangiz — DEFAULT: shu paytgacha yozgan hamma provider'ingiz singleton edi. Boshqa scope faqat ataylab yoziladi.
Yozilishi:
@Injectable({ scope: Scope.REQUEST })
class SorovXizmati { /* … */ }
@Injectable({ scope: Scope.TRANSIENT })
class Yozuvchi { /* … */ }Custom provider'da ham: { provide: X, useClass: Y, scope: Scope.REQUEST }. Controller'ga ham scope berish mumkin: @Controller({ path: "t", scope: Scope.REQUEST }).
2.2 @Inject(REQUEST)
REQUEST scope'li provider joriy Express so'rovini constructor'da ola oladi:
import { REQUEST } from "@nestjs/core";
@Injectable({ scope: Scope.REQUEST })
class SorovniOladi {
constructor(@Inject(REQUEST) readonly req: Request) {}
}REQUEST — Nest'ning tayyor tokeni. Uni so'ragan provider avtomatik REQUEST scope'ga o'tadi — boshqa yo'l yo'q: so'rov har safar boshqa.
2.3 TRANSIENT qachon foydali
TRANSIENT'ning eng ko'p uchraydigan ishlatilishi — kontekstli logger. Har xizmat logga o'z nomini yozmoqchi: [VazifalarService] …, [EksportService] …. Logger singleton bo'lsa, nom bitta maydonda turadi va oxirgi yozgan xizmat hammanikini ustidan yozadi. TRANSIENT bo'lsa, har iste'molchi o'z nusxasini oladi va o'z nomini qo'yadi.
Demo loyihada tekshirdik (biz ishga tushirdik: NestJS 12.2.0, Node 24.21.0):
@Injectable({ scope: Scope.TRANSIENT })
class KontekstLog {
#kontekst = "?";
kontekstQoy(nom: string): void { this.#kontekst = nom; }
yoz(xabar: string): string {
return `[${this.#kontekst}] ${xabar}`;
}
}
@Injectable()
class VazifalarService {
constructor(readonly log: KontekstLog) {
log.kontekstQoy("VazifalarService");
}
}
// EksportService ham xuddi shunday, o'z nomi bilanIkki xizmat bittadan qator yozdi, keyin ularning loggerlarini solishtirdik:
[VazifalarService] vazifa yaratildi
[EksportService] CSV tayyor
bir xil nusxami: falseNima bo'ldi? Har xizmat o'z KontekstLog nusxasini oldi (false), shuning uchun nomlar aralashmadi. Muhimi — ikkala xizmat ham DEFAULT qoldi: TRANSIENT, REQUEST'dan farqli, yuqmaydi va so'rov paytida qayta yaratilmaydi. Nusxalar ishga tushishda bir marta yaratiladi (kanon tajribasining A variantida ham transient: 1). Shuning uchun uning narxi deyarli yo'q. Nest'ning o'z Logger klassi ham shu g'oyaga qurilgan: new Logger(VazifalarService.name) — har klassga o'z konteksti (NestJS Logger).
Express'da bu muammo qanday yechilgan edi? 25-qismda pino'ning log.child(...) metodi bilan har so'rovga sorovId bog'langan "bola" logger yaratilgan edi. Xuddi shu metod bilan har modulga ham o'z bolasini berish mumkin: log.child({ modul: "eksport" }). TRANSIENT — xuddi shu g'oyaning Nest konteyneridagi shakli: bolani siz emas, konteyner yaratadi.
Tekshirib ko'ring:
KontekstLogni TRANSIENT emas, oddiy DEFAULT qilsak, yuqoridagi ikki qator qanday chiqadi?
Javob
Ikkalasi ham [EksportService] … bo'ladi. Singleton'da nusxa bitta, #kontekst maydoni ham bitta. Avval VazifalarService unga o'z nomini yozadi, keyin EksportService ustidan yozadi. Kim oxirgi yaratilgan bo'lsa — hamma log o'shaning nomi bilan chiqadi. Bu — darsning boshidagi "singleton'ga holat yozish" xatosi, faqat log ko'rinishida.
3. Kanon tajribasi: kim necha marta yaratildi
3.1 Tajriba sharti
Kanon bitta ilovani ikki variantda qurdi: Xizmat avval DEFAULT, keyin REQUEST. Har klass constructor'ida hisoblagich oshadi. Kod qisqartirilgan:
@Injectable({ scope: Scope.TRANSIENT })
class Yozuvchi {
constructor() { yaratildi.transient++; }
}
@Injectable() class Soat { /* yaratildi.soat++ */ }
@Injectable({ scope })
class Xizmat {
constructor(
readonly soat: Soat,
readonly y1: Yozuvchi,
readonly y2: Yozuvchi,
) { yaratildi.xizmat++; }
ol() {
return {
ok: this.soat.hozir(),
birXilTransient: this.y1 === this.y2,
};
}
}
@Controller()
class C {
constructor(private readonly x: Xizmat) {
yaratildi.controller++;
}
@Get("x") ol() { return this.x.ol(); }
}E'tibor bering: Xizmat Yozuvchi ni ikki marta so'raydi (y1, y2). Uchinchi variant (C) — alohida kichik ilova: @Inject(REQUEST) bilan so'rovni oladigan provider. Har variantga 3 so'rov yuborildi (kanon tajribasi, Vitest muhitida).
3.2 Natija
Har variantda ikki sanoq bor: ishga tushgan zahoti va 3 so'rovdan keyin. Ikkalasini solishtiring — qaysi son o'sdi, qaysi biri joyida qoldi:
A) DEFAULT (singleton) xizmat
ishga tushganda yaratilgan: {"soat":1,"xizmat":1,"controller":1,"transient":1}; 3 so'rovdan keyin: {"soat":1,"xizmat":1,"controller":1,"transient":1}; javob: {"ok":1,"birXilTransient":true}
B) REQUEST xizmat (controller ham har so'rovda yaratiladi — bubbling)
ishga tushganda yaratilgan: {"soat":1,"xizmat":0,"controller":0,"transient":0}; 3 so'rovdan keyin: {"soat":1,"xizmat":3,"controller":3,"transient":3}; javob: {"ok":1,"birXilTransient":true}
C) REQUEST + @Inject(REQUEST)
ishga tushganda yaratilgan: {"soat":0,"xizmat":0,"controller":0,"transient":0}; 3 so'rovdan keyin: {"soat":0,"xizmat":0,"controller":0,"transient":0}; javob: {"url":"/r"}Har variantni o'qiymiz.
A — DEFAULT. Hammasi ishga tushishda bir martadan yaratildi va 3 so'rovdan keyin ham 1. Singleton — kutilganidek.
B — REQUEST. Ishga tushishda faqat Soat (u DEFAULT). xizmat, controller, transient — 0: ular so'rovni kutyapti. 3 so'rovdan keyin — har biri 3. Eng muhim raqam — controller: 3. Controller'ga scope yozilmagan, u DEFAULT. Lekin u REQUEST scope'li xizmatga tayanadi — va o'zi ham har so'rovda yaratildi. Bu — scope yuqishi (scope bubbling): REQUEST scope bog'liqlik zanjiri bo'ylab yuqoriga ko'tariladi.
C — @Inject(REQUEST). Hisoblagichlar 0 — bu ilovada A/B klasslari yo'q (alohida modul). Javob {"url":"/r"} — provider joriy so'rovni oldi.
Va birXilTransient: true — A da ham, B da ham. Xizmat Yozuvchi ni ikki marta so'radi, lekin bitta nusxa oldi. TRANSIENT "har inject'ga yangi" emas, "har iste'molchi klassga yangi". Xizmat — bitta iste'molchi, ikki parametr unga bitta nusxa. Boshqa klass ham Yozuvchi ni so'rasa — o'shanga alohida.
flowchart BT
R["Xizmat: REQUEST"] --> C["Controller: DEFAULT → REQUEST"]
S["Soat: DEFAULT"] --> R
T["Yozuvchi: TRANSIENT"] --> R
C --> H["har so'rovda yangi nusxa"]Diagrammani pastdan yuqoriga o'qing: REQUEST Xizmat dan controller'ga "yuqdi". Soat esa ta'sirlanmadi — u xizmatning bog'liqligi, iste'molchisi emas. Yuqish faqat yuqoriga.
3.3 Narxi
Kanon har variantni yuk ostida o'lchadi: autocannon alohida jarayonda, 10 ulanish, 5 aylana × 3 s, variantlar navbat bilan. Mashinada boshqa kanon agentlari parallel ishlagan — mutlaq raqamlar keng oraliqda, shuning uchun nisbat muhim. Grafikda DEFAULT = 100 %:
- DEFAULT100 %
- REQUESTcontroller ham har so'rovda57 %
- REQUEST + @Inject(REQUEST)boshqa, kichikroq ilova73 %
Manba: IlmHamroh kanon o'lchovi, 2026-10-27: Node 24.21.0, @nestjs/core 12.2.0, autocannon 10 ulanish, mediana; DEFAULT 11 244, REQUEST 6 382, C 8 255 req/s; bitta mashina, bir vaqtda, mashina band
- REQUEST — DEFAULT'ning 57 %, ya'ni taxminan 43 % sekin. p99 kechikish 3 ms dan 8 ms ga o'sdi.
- C (73 %) bilan solishtirish to'liq adolatli emas: u boshqa, kichikroq ilova (bitta provider,
SoatvaYozuvchisiz). Har so'rovda kamroq obyekt yaratiladi — shuning uchun B dan tezroq.
Sekinlik qayerdan? Har so'rovda Nest yangi DI "kontekst"ni quradi: zanjirdagi har REQUEST nusxa yaratiladi, bog'liqliklari yechiladi, so'rov tugagach chiqindi yig'uvchi ularni tozalaydi. Singleton'da bu ish bir marta, ishga tushishda.
Kichik hisob. B variantda har so'rov uchun uchta obyekt yaratildi: xizmat, controller va yozuvchi. Sekundiga 6 000 so'rovda bu sekundiga 18 000 ta yangi obyekt — ularning hammasi bir necha millisekunddan keyin chiqindiga aylanadi. DEFAULT variantda bu son nol: so'rov tayyor obyektlardan o'tadi va hech narsa qoldirmaydi. Haqiqiy loyihada zanjir uzunroq — vazifalar-api da controller, xizmat, ombor, log bor — demak farq ham kattaroq bo'lishi mumkin.
Kanon bitta o'lchov xatosini ham topdi. Birinchi urinishda autocannon server bilan bitta jarayonda ishlagan va DEFAULT atigi 2 445 req/s ko'rsatgan — 5 baravar kam. Yuk generatori server bilan bir event loop'ni bo'lishgan. Yuk o'lchovida generator doim alohida jarayonda bo'lsin.
Tekshirib ko'ring:
AuthServiceREQUEST scope'li. UniVazifalarService(DEFAULT) ishlatadi, uni esaVazifalarControllervaEksportService. Nechta klass har so'rovda qayta yaratiladi?
Javob
To'rttasi: AuthService, VazifalarService, VazifalarController va EksportService. REQUEST AuthService dan zanjir bo'ylab yuqoriga yuqadi: unga tayangan VazifalarService REQUEST bo'ladi, unga tayangan controller va EksportService ham. Bitta xizmatga yozilgan scope butun ilovaning yarmini sekinlashtirishi mumkin. EksportService dagi #ishlar Map'i esa endi har so'rovda bo'sh — eksport butunlay buziladi.
4. JS modelida scope va yuqish
Scope qoidalarini kichik modelda takrorlaymiz — brauzerda ishlaydi. haqiqiyScope yuqishni hisoblaydi, ol nusxani mos jadvaldan oladi:
const royxat = new Map(); // klass → { scope, kerak }
const yaratildi = {};
const qosh = (K, scope, kerak = []) =>
royxat.set(K, { scope, kerak });
// scope "yuqadi": bog'liqlik REQUEST bo'lsa, iste'molchi ham REQUEST
function haqiqiyScope(K) {
const { scope, kerak } = royxat.get(K);
if (scope !== "DEFAULT") return scope;
const yuqdi = kerak.some((B) => haqiqiyScope(B) === "REQUEST");
return yuqdi ? "REQUEST" : "DEFAULT";
}
const singletonlar = new Map();
function ol(K, sorov, ota) {
const s = haqiqiyScope(K);
let jadval = s === "DEFAULT" ? singletonlar : sorov;
let kalit = K;
if (s === "TRANSIENT") {
// har iste'molchi KLASSIGA alohida nusxa
jadval = ota.jadval;
kalit = `${K.name}@${ota.nom}`;
}
if (jadval.has(kalit)) return jadval.get(kalit);
const args = royxat.get(K).kerak
.map((B) => ol(B, sorov, { nom: K.name, jadval }));
const n = new K(...args);
yaratildi[K.name] = (yaratildi[K.name] ?? 0) + 1;
jadval.set(kalit, n);
return n;
}
class Soat {}
class Yozuvchi {}
class Xizmat {
constructor(soat, y1, y2) { this.birXil = y1 === y2; }
}
class Controller {
constructor(x) { this.x = x; }
}
for (const scope of ["DEFAULT", "REQUEST"]) {
singletonlar.clear();
for (const k of Object.keys(yaratildi)) delete yaratildi[k];
qosh(Soat, "DEFAULT");
qosh(Yozuvchi, "TRANSIENT");
qosh(Xizmat, scope, [Soat, Yozuvchi, Yozuvchi]);
qosh(Controller, "DEFAULT", [Xizmat]);
let c;
for (let i = 0; i < 3; i++) c = ol(Controller, new Map());
console.log(`${scope}: ${JSON.stringify(yaratildi)}`);
console.log(` controller: ${haqiqiyScope(Controller)},` +
` birXil: ${c.x.birXil}`);
}Konsolda:
DEFAULT: {"Soat":1,"Yozuvchi":1,"Xizmat":1,"Controller":1}
controller: DEFAULT, birXil: true
REQUEST: {"Soat":1,"Yozuvchi":3,"Xizmat":3,"Controller":3}
controller: REQUEST, birXil: trueSonlar kanon tajribasidagi A va B bilan bir xil. Uch joyga qarang:
new Map()— har "so'rov" uchun yangi jadval. REQUEST nusxalar shu jadvalda yashaydi va so'rov bilan birga yo'qoladi.haqiqiyScope(Controller)—REQUEST, garchiqosh(Controller, "DEFAULT", …)yozilgan bo'lsa ham. Yuqish rekursiya bilan hisoblanadi (rekursiya).- TRANSIENT kalit —
Yozuvchi@Xizmat: iste'molchi klass nomi bilan. Shuning uchuny1vay2bitta nusxa.
Model soddalashtirilgan: haqiqiy Nest TRANSIENT'ni har iste'molchi nusxasiga bog'laydi, modul chegarasi va "durable" kontekstlar ham bor.
5. Express'da qanday bo'lardi va nima yashirin
25-qismda "har so'rovga yangi obyekt" kerak bo'lsa, uni handler ichida o'zingiz yaratardingiz:
r.get("/hisobot", (req, res) => {
const hisobot = new HisobotYasovchi(req.sorovId, ombor);
res.json(hisobot.yasa());
});Bu yerda narx ko'rinib turibdi: new — handler ichida, bitta obyekt, bitta joyda. Kod o'qigan odam "har so'rovda yangi obyekt" ekanini darrov ko'radi.
Nest'da REQUEST scope bu ishni yashirin qiladi. Siz faqat bitta klassga scope: Scope.REQUEST yozasiz. Qolgani — unga tayangan har klass ham har so'rovda yaratilishi — hech qayerda yozilmagan. Kanon tajribasidagi controller'ning scope maydoni yo'q edi, lekin u 3 so'rovda 3 marta yaratildi. Yangi dasturchi controller fayliga qarab buni bila olmaydi.
Bu — freymvorkning umumiy savdosi: kam kod evaziga kam ko'rinish. DI konteyner ulash ishini o'z zimmasiga olgani yaxshi; lekin u nusxa sonini ham yashirincha o'zgartira olishini yodda tuting.
5.1 Scope va lifecycle hook'lar
Yana bir yashirin farq: REQUEST scope'li klasslarda onModuleInit kabi hayot sikli ilgaklari (lifecycle hook'lar) chaqirilmaydi. Nest hujjati shunday deydi, biz demo loyihada tekshirdik. Ikki xizmatga onModuleInit yozdik: biri DEFAULT, ikkinchisi REQUEST. Ilovani ko'tarib, ikkita so'rov yubordik va qaysi ilgak chaqirilganini sanadik:
ishga tushgach onModuleInit: ["DEFAULT"]
2 so'rovdan keyin onModuleInit: ["DEFAULT"]REQUEST xizmat har so'rovda yaratildi (kanon sanog'idagi B variant kabi), lekin uning onModuleInit i bir marta ham chaqirilmadi. Sabab: bunday klasslar ishga tushishda emas, so'rov paytida yaratiladi — "modul tayyor bo'ldi" degan lahzada ular hali yo'q. Agar xizmatga REQUEST yuqsa va uning onModuleInit i baza ulanishini ochayotgan bo'lsa — ulanish ochilmay qoladi, xato esa keyinroq, boshqa joyda chiqadi. Hook'larning o'zi — Lifecycle hook'lar darsida.
5.2 Scope'ni qanday topish mumkin
Katta loyihada "qaysi provider'lar REQUEST" degan savolga javob kerak bo'ladi. Uch yo'l:
- Qidiruv: kodda
Scope.REQUEST,Scope.TRANSIENTvaREQUESTtokenini qidirish. Bu ataylab yozilganlarni topadi, lekin yuqqanlarni emas. - Hisoblagich: shubhali klass constructor'iga vaqtincha log qo'yish. Kanon tajribasi aynan shunday qilgan — "3 so'rovda 3 marta".
- Discovery: Nest'ning
DiscoveryServicei hamma provider'lar ro'yxatini va ularningisDependencyTreeStatic()belgisini beradi — "bog'liqlik daraxti statikmi", ya'ni yuqish bormi. Buni Reflector va DiscoveryService darsida ishlatamiz.
6. Durable provider'lar — qisqacha
REQUEST scope'ning narxini kamaytirish uchun Nest durable (bardoshli) provider'larni taklif qiladi. G'oya: so'rovlarni biror belgi bo'yicha guruhlash (masalan, mijoz kompaniyasi — tenant) va har guruhga bitta DI kontekst. Bir xil tenant'ning so'rovlari bitta nusxani bo'lishadi. Buning uchun ContextIdStrategy yoziladi va provider'ga durable: true beriladi.
Bu ko'p mijozli (multi-tenant) tizimlarda foydali: har mijozning o'z baza ulanishi, o'z sozlamasi. Kanon durable provider'larni sinamagan — yuqoridagilar Nest hujjati bo'yicha (2026-11 holatiga). vazifalar-api bitta egali — bunday ehtiyoj yo'q.
7. Nega vazifalar-api da hamma provider DEFAULT
Kanon qarori: scope o'zgartirilmaydi. Uch sabab:
- Narx. REQUEST ~43 % sekin (kanon o'lchovi), p99 2,5 baravar. Yuqish tufayli bitta xizmat butun zanjirni sekinlashtiradi.
- Holatli singleton'lar.
EksportServicening#ishlarMap'i so'rovlar orasida yashashi shart. Unga REQUEST yuqsa — eksport buziladi. - Muqobil bor. Joriy so'rov konteksti 24–25-qismdan beri AsyncLocalStorage orqali olinadi:
sorovLogimiddleware'i kontekstni ochadi,joriyLog(log)uni o'qiydi. Singleton xizmat har chaqiruvda joriy so'rov logger'ini oladi — yangi nusxa yaratmasdan. Buni Nest'ga moslashtirilgan kutubxona bilan qilish — So'rov konteksti: nestjs-cls darsida.
7.1 Joriy so'rov ma'lumotini olishning uch yo'li
"Xizmatga joriy foydalanuvchi yoki sorovId kerak" — bu vazifaning uch yechimi bor. Ularni yonma-yon qo'yamiz:
| Yo'l | Qanday | Narxi |
|---|---|---|
| parametr | controller @Req() dan olib, metodga uzatadi |
yo'q, lekin har metod imzosida qo'shimcha argument |
| AsyncLocalStorage | middleware kontekst ochadi, xizmat o'qiydi | juda kichik; singleton qoladi |
| REQUEST scope | @Inject(REQUEST) constructor'da |
~43 % sekin, yuqadi, ilgaklar ishlamaydi |
Birinchi yo'l eng ochiq: kodni o'qigan odam ma'lumot qayerdan kelganini ko'radi. Lekin chaqiruv zanjiri uzun bo'lsa (controller → xizmat → ombor → log), argument har qavatdan o'tishi kerak. Ikkinchi yo'l shu muammoni yechadi va vazifalar-api aynan uni tanlagan. Uchinchisi Nest hujjatidagi eng sodda ko'rinadigan yo'l, lekin narxi eng katta.
REQUEST qachon to'g'ri tanlov? Kam so'rovli, lekin har so'rovda tubdan boshqa resurs kerak bo'lganda (tenant bo'yicha boshqa baza) — va durable bilan. Yoki tashqi kutubxona aynan REQUEST scope talab qilsa (masalan, ba'zi GraphQL dataloader naqshlari).
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Ilova sekinlashdi, sabab ko'rinmaydi | bitta REQUEST provider butun zanjirga yuqdi | scope'ni olib tashlang yoki AsyncLocalStorage |
Singleton'dagi Map har so'rovda bo'sh |
xizmatga REQUEST yuqdi | holatli xizmatni REQUEST zanjiridan ajrating |
ModuleRef.get() xato beradi |
REQUEST/TRANSIENT provider get bilan olinmaydi |
resolve() (Circular dependency, ModuleRef) |
| TRANSIENT ikki parametrda bir xil nusxa | "har iste'molchi klassga", "har parametrga" emas | ikki xil token yoki fabrika |
onModuleInit ishlamadi, ulanish ochilmadi |
xizmat REQUEST (yoki unga yuqqan) | resursni DEFAULT provider'ga ko'chiring |
| Hamma log bitta xizmat nomi bilan | kontekstli logger singleton | TRANSIENT logger yoki log.child |
| Yuk o'lchovi juda past | autocannon server bilan bitta jarayonda | generatorni alohida jarayonda yurgizing |
9. Mashqlar
Mashqlar kurs/mashqlar/30/10-scope/ da.
1-mashq (oson): scope'ni toping
- Butun ilovaga bitta, ishga tushishda yaratiladi —
Scope. - Har so'rovga yangi nusxa —
Scope. @Inject(REQUEST)ishlatgan provider avtomatik qaysi scope'da? —XizmatYozuvchi(TRANSIENT) ni ikki parametrda so'radi. Nechta nusxa oladi? —- Kanon o'lchovida REQUEST DEFAULT'dan taxminan necha foiz sekin? —
Yechim
- DEFAULT — sukut, singleton.
- REQUEST — so'rov tugagach tashlanadi.
- REQUEST — joriy so'rov har safar boshqa, singleton bo'la olmaydi.
- 1 — TRANSIENT har iste'molchi klassga; kanonda
birXilTransient: true. - 43 — 6 382 / 11 244 ≈ 0.57.
2-mashq (o'rta): yuqishni hisoblang
yuqishXaritasi(graf, scopelar) funksiyasini yozing. graf — { Klass: [bog'liqliklar] }, scopelar — faqat ataylab berilgan scope'lar ({ Auth: "REQUEST" }). Har klass uchun haqiqiy scope'ni qaytarsin. Sinov grafi: Controller → Xizmat → [Auth, Soat], Eksport → [Xizmat], Soat → [], Auth → []. Avval scope'siz, keyin Auth: "REQUEST" bilan. Ishora: rekursiya va some.
Yechim
function yuqishXaritasi(graf, scopelar) {
const natija = {};
function scope(k) {
if (natija[k]) return natija[k];
const oz = scopelar[k] ?? "DEFAULT";
const yuqdi = graf[k].some((b) => scope(b) === "REQUEST");
natija[k] = oz === "DEFAULT" && yuqdi ? "REQUEST" : oz;
return natija[k];
}
return Object.fromEntries(
Object.keys(graf).map((k) => [k, scope(k)]),
);
}
const graf = {
Controller: ["Xizmat"],
Eksport: ["Xizmat"],
Xizmat: ["Auth", "Soat"],
Auth: [],
Soat: [],
};
console.log(yuqishXaritasi(graf, {}));
console.log(yuqishXaritasi(graf, { Auth: "REQUEST" }));Konsolda:
{
Controller: 'DEFAULT',
Eksport: 'DEFAULT',
Xizmat: 'DEFAULT',
Auth: 'DEFAULT',
Soat: 'DEFAULT'
}
{
Controller: 'REQUEST',
Eksport: 'REQUEST',
Xizmat: 'REQUEST',
Auth: 'REQUEST',
Soat: 'DEFAULT'
}Bitta Auth: "REQUEST" yozuvi beshtadan to'rtta klassni REQUEST qildi. Soat qoldi — u faqat bog'liqlik. Katta loyihada bunday xaritani DiscoveryService bilan haqiqiy provider'lardan qurish mumkin.
3-mashq (qiyin): TRANSIENT — klass va nusxa
Darsdagi modelda TRANSIENT kalit iste'molchi klass nomi bo'yicha. Ikki xil klass (Hisobot, Eksport) Yozuvchi ni so'rasa, nechta Yozuvchi yaratiladi? Modelni ishlatib tekshiring: ikkala klass DEFAULT, har biri Yozuvchi ni bir marta so'raydi, bitta Ilova klassi ikkalasiga tayanadi. Keyin Hisobot ni REQUEST qilib, 2 so'rov uchun sanang. Ishora: darsdagi qosh/ol ni o'zgarishsiz oling.
Yechim
const royxat = new Map();
const yaratildi = {};
const qosh = (K, scope, kerak = []) =>
royxat.set(K, { scope, kerak });
function haqiqiyScope(K) {
const { scope, kerak } = royxat.get(K);
if (scope !== "DEFAULT") return scope;
const yuqdi = kerak.some((B) => haqiqiyScope(B) === "REQUEST");
return yuqdi ? "REQUEST" : "DEFAULT";
}
const singletonlar = new Map();
function ol(K, sorov, ota) {
const s = haqiqiyScope(K);
let jadval = s === "DEFAULT" ? singletonlar : sorov;
let kalit = K;
if (s === "TRANSIENT") {
jadval = ota.jadval;
kalit = `${K.name}@${ota.nom}`;
}
if (jadval.has(kalit)) return jadval.get(kalit);
const args = royxat.get(K).kerak
.map((B) => ol(B, sorov, { nom: K.name, jadval }));
const n = new K(...args);
yaratildi[K.name] = (yaratildi[K.name] ?? 0) + 1;
jadval.set(kalit, n);
return n;
}
class Yozuvchi {}
class Hisobot { constructor(y) { this.y = y; } }
class Eksport { constructor(y) { this.y = y; } }
class Ilova { constructor(h, e) { this.h = h; this.e = e; } }
for (const hScope of ["DEFAULT", "REQUEST"]) {
singletonlar.clear();
for (const k of Object.keys(yaratildi)) delete yaratildi[k];
qosh(Yozuvchi, "TRANSIENT");
qosh(Hisobot, hScope, [Yozuvchi]);
qosh(Eksport, "DEFAULT", [Yozuvchi]);
qosh(Ilova, "DEFAULT", [Hisobot, Eksport]);
let a;
for (let i = 0; i < 2; i++) a = ol(Ilova, new Map());
console.log(`Hisobot ${hScope}: ${JSON.stringify(yaratildi)}`,
`bir xil: ${a.h.y === a.e.y}`);
}Konsolda:
Hisobot DEFAULT: {"Yozuvchi":2,"Hisobot":1,"Eksport":1,"Ilova":1} bir xil: false
Hisobot REQUEST: {"Yozuvchi":3,"Hisobot":2,"Eksport":1,"Ilova":2} bir xil: falseBirinchi qator: ikki iste'molchi klass — ikki Yozuvchi, ular har xil (false). Ikkinchisi: Hisobot REQUEST bo'ldi — u har so'rovda yangi, demak uning Yozuvchi si ham har so'rovda yangi (2 so'rov → 2), Eksport niki esa bitta. Jami 3. Ilova ham 2 — unga Hisobot dan yuqdi.
10. Real ishda
- Ko'p mijozli SaaS (har kompaniyaning o'z bazasi) — REQUEST scope va durable provider'lar uchrashadigan asosiy joy.
- Kod review'da
scope: Scope.REQUESTko'rinsa, birinchi savol: "zanjirda kim bor va AsyncLocalStorage yetmaydimi?". - Loglarda TRANSIENT eng ko'p uchraydi: kutubxonalar (masalan, nestjs-pino'ning
PinoLoggeri) har xizmatga o'z kontekstli logger'ini shu yo'l bilan beradi. Uni ko'rganda qo'rqmang — TRANSIENT REQUEST kabi qimmat emas. - Profiling'da Nest ilovasi kutilmaganda sekin bo'lsa, scope'li provider'larni qidirish — tez tekshiriladigan sabablardan biri.
- Intervyu: "Nest'da qanday scope'lar bor?", "Scope bubbling nima?", "Joriy foydalanuvchini singleton xizmatda qanday olasiz?" (AsyncLocalStorage, nestjs-cls yoki parametr).
Xulosa
DEFAULT— bitta nusxa (sukut);REQUEST— har so'rovga;TRANSIENT— har iste'molchi klassga.- REQUEST zanjir bo'ylab yuqoriga yuqadi: unga tayangan controller va xizmatlar ham har so'rovda yaratiladi.
@Inject(REQUEST)— joriy so'rov; uni so'ragan provider REQUEST bo'ladi.- TRANSIENT yuqmaydi va ishga tushishda yaratiladi — kontekstli logger uchun qulay; REQUEST klasslarda
onModuleInitchaqirilmaydi. - Kanon o'lchovida REQUEST ~43 % sekin, p99 3 → 8 ms; o'lchovda yuk generatori alohida jarayonda bo'lsin.
vazifalar-apida hammasi DEFAULT: holatli singleton'lar va AsyncLocalStorage muqobili; durable — sinalmagan.
Keyingi dars: Circular dependency, ModuleRef va lazy modullar — aylanma bog'liqlik, forwardRef, ModuleRef.get/resolve/create va ESM'da testda o'tib, build'da yiqiladigan tuzoq.
Manbalar
- NestJS hujjati — Injection scopes (DEFAULT, REQUEST, TRANSIENT, durable providers): https://docs.nestjs.com/fundamentals/injection-scopes
- Node.js hujjati — AsyncLocalStorage: https://nodejs.org/api/async_context.html
- Kanon o'lchovi (IlmHamroh, 2026-10-27): uch scope, yaratilish sanog'i va autocannon (10 ulanish, 5 × 3 s, mediana), NestJS 12.2.0, Node 24.21.0, Vitest muhitida; bitta mashina, parallel yuk bilan.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!