Maxsus provayderlar
Oldingi boblarda biz Dependency Injection (DI) ning turli jihatlariga va uning Nestda qanday ishlatilishiga to'xtalgan edik. Bunga misol sifatida sinflarga instansiyalarni (ko'pinc
Oldingi boblarda biz Dependency Injection (DI) ning turli jihatlariga va uning Nestda qanday ishlatilishiga to'xtalgan edik. Bunga misol sifatida sinflarga instansiyalarni (ko'pincha servis provayderlarini) in'eksiya qilish uchun ishlatiladigan constructor based dependency injectionni keltirish mumkin. Dependency Injection Nest yadroga fundamental tarzda singdirilganini bilib hayron bo'lmaysiz. Hozirgacha biz faqat bitta asosiy patternni ko'rib chiqdik. Ilovangiz murakkablashgani sari, DI tizimining to'liq imkoniyatlaridan foydalanishingiz kerak bo'lishi mumkin, keling ularni batafsilroq ko'rib chiqaylik.
DI asoslari
Dependency injection - bu inversion of control (IoC) texnikasi bo'lib, unda bog'liqliklarni instansiyalashni imperativ tarzda o'zingizning kodingizda qilish o'rniga, IoC konteyneriga (bizning holatda NestJS runtime tizimi) delegatsiya qilasiz. Keling, Providers chapter dagi ushbu misolda nimalar bo'layotganini ko'rib chiqamiz.
Avval provayderni aniqlaymiz. @Injectable() dekoratori CatsService sinfini provayder sifatida belgilaydi.
1import { Injectable } from '@nestjs/common';
2import { Cat } from './interfaces/cat.interface';
3
4@Injectable()
5export class CatsService {
6 private readonly cats: Cat[] = [];
7
8 findAll(): Cat[] {
9 return this.cats;
10 }
11}Keyin Nestdan provayderni controller sinfiga in'eksiya qilishni so'raymiz:
1import { Controller, Get } from '@nestjs/common';
2import { CatsService } from './cats.service';
3import { Cat } from './interfaces/cat.interface';
4
5@Controller('cats')
6export class CatsController {
7 constructor(private catsService: CatsService) {}
8
9 @Get()
10 async findAll(): Promise<Cat[]> {
11 return this.catsService.findAll();
12 }
13}Nihoyat, provayderni Nest IoC konteynerida ro'yxatdan o'tkazamiz:
1import { Module } from '@nestjs/common';
2import { CatsController } from './cats/cats.controller';
3import { CatsService } from './cats/cats.service';
4
5@Module({
6 controllers: [CatsController],
7 providers: [CatsService],
8})
9export class AppModule {}Bularning ishlashi uchun ichkarida aynan nima sodir bo'ladi? Jarayonda uchta asosiy qadam bor:
cats.service.tsda@Injectable()dekoratoriCatsServicesinfini Nest IoC konteyneri tomonidan boshqarilishi mumkin bo'lgan sinf sifatida e'lon qiladi.cats.controller.tsdaCatsControllerkonstruktor in'eksiyasi orqaliCatsServicetokeniga bog'liqlik e'lon qiladi:
1 constructor(private catsService: CatsService)app.module.tsdaCatsServicetokeninicats.service.tsfaylidagiCatsServicesinfi bilan bog'laymiz. Bu bog'lash (ro'yxatdan o'tkazish) qanday amalga oshishini aniq quyida ko'ramiz.
Nest IoC konteyneri CatsController ni instansiyalaganda, avvalo har qanday bog'liqliklarni qidiradi*. CatsService bog'liqligini topganda, CatsService tokeni bo'yicha qidiruvni amalga oshiradi va yuqoridagi #3 ro'yxatdan o'tkazish qadamiga ko'ra CatsService sinfini qaytaradi. SINGLETON scope (standart xulq) faraz qilinsa, Nest CatsService instansiyasini yaratadi, keshlaydi va qaytaradi, yoki agar avval keshlangan bo'lsa, mavjud instansiyani qaytaradi.
*Bu tushuntirish nuqtani ko'rsatish uchun biroz soddalashtirilgan. Biz yuzaki o'tib ketgan muhim jihatlardan biri shundaki, bog'liqliklar uchun kodni tahlil qilish jarayoni juda murakkab bo'lib, ilova bootstrappingi vaqtida sodir bo'ladi. Muhim xususiyatlardan biri - bog'liqlik tahlili (yoki "bog'liqlik grafigini yaratish") tranzitiv hisoblanadi. Yuqoridagi misolda, agar CatsService ning o'zi ham bog'liqliklarga ega bo'lsa, ular ham yechiladi. Bog'liqlik grafigi bog'liqliklar to'g'ri tartibda, ya'ni "pastdan yuqoriga" yechilishini ta'minlaydi. Bu mexanizm dasturchini murakkab bog'liqlik grafigini boshqarish zaruratidan xalos qiladi.
Standart provayderlar
@Module() dekoratoriga yaqindan nazar tashlaylik. app.module da biz quyidagilarni e'lon qilamiz:
1@Module({
2 controllers: [CatsController],
3 providers: [CatsService],
4})providers xossasi providers massivini qabul qiladi. Hozirgacha biz bu provayderlarni sinf nomlari ro'yxati orqali berdik. Aslida, providers: [CatsService] sintaksisi quyidagi to'liq sintaksisning qisqa yozuvidir:
1providers: [
2 {
3 provide: CatsService,
4 useClass: CatsService,
5 },
6];Endi bu aniq konstruktsiyani ko'rsak, ro'yxatdan o'tkazish jarayonini tushunishimiz mumkin. Bu yerda biz CatsService tokenini CatsService sinfi bilan aniq bog'layapmiz. Qisqa yozuv shunchaki eng ko'p uchraydigan holatni soddalashtirish uchun qulaylik, ya'ni token bir xil nomdagi sinf instansiyasini so'rash uchun ishlatiladigan holat.
Maxsus provayderlar
Talablaringiz Standart provayderlar taqdim etadigan imkoniyatlardan kengroq bo'lsa nima bo'ladi? Quyida bir nechta misol:
- Nest sinfni instansiyalash (yoki keshlangan instansiyani qaytarish) o'rniga maxsus instansiyani yaratishni xohlaysiz
- Mavjud sinfni ikkinchi bog'liqlikda qayta ishlatmoqchisiz
- Test uchun sinfni mock versiyasi bilan almashtirmoqchisiz
Nest bunday holatlar uchun Maxsus provayderlarni aniqlash imkonini beradi. U maxsus provayderlarni aniqlashning bir nechta yo'lini taqdim etadi. Keling, ularni ko'rib chiqamiz.
Agar bog'liqliklarni yechishda muammolarga duch kelsangiz, NEST_DEBUG muhit o'zgaruvchisini o'rnatib, ishga tushirish vaqtida qo'shimcha yechish loglarini olishingiz mumkin.
Qiymat provayderlari: useValue
useValue sintaksisi doimiy qiymatni in'eksiya qilish, tashqi kutubxonani Nest konteyneriga qo'shish yoki real implementatsiyani mock obyekt bilan almashtirish uchun foydali. Faraz qilaylik, test maqsadida Nestni mock CatsService dan foydalanishga majburlamoqchisiz.
1import { CatsService } from './cats.service';
2
3const mockCatsService = {
4 /* mock implementation
5 ...
6 */
7};
8
9@Module({
10 imports: [CatsModule],
11 providers: [
12 {
13 provide: CatsService,
14 useValue: mockCatsService,
15 },
16 ],
17})
18export class AppModule {}Ushbu misolda CatsService tokeni mockCatsService mock obyektiga yechiladi. useValue qiymat talab qiladi - bu yerda u o'rnini bosayotgan CatsService sinfi bilan bir xil interfeysga ega literal obyekt. TypeScriptning structural typing tufayli, mos interfeysga ega istalgan obyektni, jumladan literal obyektni yoki new bilan instansiyalangan sinf instansiyasini ishlatishingiz mumkin.
Sinfga asoslanmagan provayder tokenlari
Hozirgacha biz provayder tokenlari sifatida sinf nomlaridan foydalandik (providers massivida ko'rsatilgan provayderdagi provide xossasi qiymati). Bu constructor based injection da ishlatiladigan standart pattern bilan mos keladi, unda token ham sinf nomi bo'ladi. (Agar bu tushuncha to'liq aniq bo'lmasa, tokenlar haqida eslatma uchun DI Fundamentals ga qayting). Ba'zan DI tokeni sifatida satrlar yoki symbol'lar ishlatish moslashuvchanligini xohlashimiz mumkin. Masalan:
1import { connection } from './connection';
2
3@Module({
4 providers: [
5 {
6 provide: 'CONNECTION',
7 useValue: connection,
8 },
9 ],
10})
11export class AppModule {}Ushbu misolda biz satr qiymatdagi tokenni ('CONNECTION') tashqi fayldan import qilingan oldindan mavjud connection obyektiga bog'layapmiz.
Token qiymati sifatida satrlarni ishlatishdan tashqari, JavaScript symbols yoki TypeScript enums ni ham ishlatishingiz mumkin.
Biz provayderni standart constructor based injection patterni orqali qanday in'eksiya qilishni avval ko'rganmiz. Bu pattern bog'liqlik sinf nomi bilan e'lon qilinishini talab qiladi. 'CONNECTION' maxsus provayderi satr qiymatdagi tokenni ishlatadi. Keling, bunday provayderni qanday in'eksiya qilishni ko'raylik. Buning uchun @Inject() dekoratoridan foydalanamiz. Bu dekorator bitta argument qabul qiladi - token.
1@Injectable()
2export class CatsRepository {
3 constructor(@Inject('CONNECTION') connection: Connection) {}
4}@Inject() dekoratori @nestjs/common paketidan import qilinadi.
Yuqoridagi misollarda tushuntirish uchun bevosita 'CONNECTION' satridan foydalangan bo'lsak-da, kodni toza tashkil etish uchun eng yaxshi amaliyot - tokenlarni constants.ts kabi alohida faylda aniqlashdir. Ularni xuddi o'z faylida aniqlanib, kerakli joylarda import qilinadigan symbol yoki enumlar kabi ko'ring.
Sinf provayderlari: useClass
useClass sintaksisi token qaysi sinfga yechilishini dinamik tarzda aniqlash imkonini beradi. Masalan, bizda abstrakt (yoki default) ConfigService sinfi bor deb faraz qilaylik. Joriy muhitga qarab Nest konfiguratsiya servisining boshqa implementatsiyasini taqdim etishini xohlaymiz. Quyidagi kod bunday strategiyani amalga oshiradi.
1const configServiceProvider = {
2 provide: ConfigService,
3 useClass:
4 process.env.NODE_ENV === 'development'
5 ? DevelopmentConfigService
6 : ProductionConfigService,
7};
8
9@Module({
10 providers: [configServiceProvider],
11})
12export class AppModule {}Ushbu kod namunadagi bir nechta detalga qaraylik. E'tibor bering, biz avval configServiceProvider ni literal obyekt bilan aniqladik, keyin uni modul dekoratorining providers xossasiga uzatdik. Bu faqat kodni tartiblash, va funksional jihatdan bu bobda hozirgacha ishlatgan misollar bilan bir xil.
Shuningdek, biz token sifatida ConfigService sinf nomidan foydalandik. ConfigService ga bog'liq bo'lgan har qanday sinf uchun Nest taqdim etilgan sinf instansiyasini (DevelopmentConfigService yoki ProductionConfigService) in'eksiya qiladi va boshqa joyda (masalan, @Injectable() dekoratori bilan e'lon qilingan ConfigService) bo'lishi mumkin bo'lgan default implementatsiyani bekor qiladi.
Factory provayderlari: useFactory
useFactory sintaksisi provayderlarni dinamik tarzda yaratish imkonini beradi. Haqiqiy provayder factory funksiyasi qaytargan qiymat bilan taqdim etiladi. Factory funksiyasi kerak bo'lsa sodda ham, murakkab ham bo'lishi mumkin. Sodda factory boshqa provayderlarga bog'liq bo'lmasligi mumkin. Murakkab factory o'z natijasini hisoblash uchun boshqa provayderlarni in'eksiya qilishi mumkin. Ikkinchi holat uchun factory provayder sintaksisi ikki bog'liq mexanizmga ega:
- Factory funksiyasi (ixtiyoriy) argumentlarni qabul qilishi mumkin.
- (Ixtiyoriy)
injectxossasi Nest yechib, instansiyalash jarayonida factory funksiyasiga argument sifatida uzatadigan provayderlar massivini qabul qiladi. Shuningdek, bu provayderlar ixtiyoriy deb belgilanishi mumkin. Ikki ro'yxat o'zaro mos bo'lishi kerak: Nestinjectro'yxatidagi instansiyalarni factory funksiyasiga xuddi shu tartibda argument sifatida uzatadi. Quyidagi misol buni ko'rsatadi.
1const connectionProvider = {
2 provide: 'CONNECTION',
3 useFactory: (optionsProvider: MyOptionsProvider, optionalProvider?: string) => {
4 const options = optionsProvider.get();
5 return new DatabaseConnection(options);
6 },
7 inject: [MyOptionsProvider, { token: 'SomeOptionalProvider', optional: true }],
8 // \______________/ \__________________/
9 // This provider The provider with this token
10 // is mandatory. can resolve to `undefined`.
11};
12
13@Module({
14 providers: [
15 connectionProvider,
16 MyOptionsProvider, // class-based provider
17 // { provide: 'SomeOptionalProvider', useValue: 'anything' },
18 ],
19})
20export class AppModule {}Alias provayderlari: useExisting
useExisting sintaksisi mavjud provayderlar uchun aliaslar yaratish imkonini beradi. Bu bir xil provayderga kirishning ikki yo'lini yaratadi. Quyidagi misolda (satr asosidagi) 'AliasedLoggerService' tokeni (sinf asosidagi) LoggerService tokeni uchun alias hisoblanadi. Faraz qilaylik, bizda ikkita turli bog'liqlik bor: biri 'AliasedLoggerService', boshqasi LoggerService. Agar har ikkala bog'liqlik SINGLETON scopeda bo'lsa, ikkalasi ham bir xil instansiyaga yechiladi.
1@Injectable()
2class LoggerService {
3 /* implementation details */
4}
5
6const loggerAliasProvider = {
7 provide: 'AliasedLoggerService',
8 useExisting: LoggerService,
9};
10
11@Module({
12 providers: [LoggerService, loggerAliasProvider],
13})
14export class AppModule {}Servisga asoslanmagan provayderlar
Provayderlar ko'pincha servislarni taqdim etsa-da, ular faqat shunga cheklanmaydi. Provayder istalgan qiymatni taqdim etishi mumkin. Masalan, quyida ko'rsatilgandek provayder joriy muhitga qarab konfiguratsiya obyektlari massivini taqdim etishi mumkin:
1const configFactory = {
2 provide: 'CONFIG',
3 useFactory: () => {
4 return process.env.NODE_ENV === 'development' ? devConfig : prodConfig;
5 },
6};
7
8@Module({
9 providers: [configFactory],
10})
11export class AppModule {}Maxsus provayderni eksport qilish
Har qanday provayder kabi, maxsus provayder ham uni e'lon qilgan modul doirasiga tegishli. Uni boshqa modullarga ko'rinarli qilish uchun eksport qilish kerak. Maxsus provayderni eksport qilish uchun biz uning tokenidan yoki to'liq provayder obyektidan foydalanishimiz mumkin.
Quyidagi misol token yordamida eksport qilishni ko'rsatadi:
1const connectionFactory = {
2 provide: 'CONNECTION',
3 useFactory: (optionsProvider: OptionsProvider) => {
4 const options = optionsProvider.get();
5 return new DatabaseConnection(options);
6 },
7 inject: [OptionsProvider],
8};
9
10@Module({
11 providers: [connectionFactory],
12 exports: ['CONNECTION'],
13})
14export class AppModule {}Muqobil ravishda, to'liq provayder obyektini eksport qiling:
1const connectionFactory = {
2 provide: 'CONNECTION',
3 useFactory: (optionsProvider: OptionsProvider) => {
4 const options = optionsProvider.get();
5 return new DatabaseConnection(options);
6 },
7 inject: [OptionsProvider],
8};
9
10@Module({
11 providers: [connectionFactory],
12 exports: [connectionFactory],
13})
14export class AppModule {}