Async Local Storage
AsyncLocalStorage Node.js API ( asynchooks API ga asoslangan) bo'lib, lokal holatni funksiyaga parametr sifatida aniq uzatmasdan butun ilova bo'ylab uzatishning muqobil usulini taq
AsyncLocalStorage Node.js API ( async_hooks API ga asoslangan) bo'lib, lokal holatni funksiyaga parametr sifatida aniq uzatmasdan butun ilova bo'ylab uzatishning muqobil usulini taqdim etadi. Bu boshqa tillardagi thread-local storage ga o'xshaydi.
Async Local Storage ning asosiy g'oyasi shuki, biz ba'zi funksiyalarni AsyncLocalStorage#run chaqirig'i bilan o'rab olamiz. O'ralgan chaqiriq ichida ishga tushadigan barcha kodlar bir xil store ga (har bir chaqiriq zanjiri uchun noyob) kirish imkoniga ega bo'ladi.
NestJS kontekstida bu shuni anglatadiki, agar so'rov hayotiy sikli davomida qolgan so'rov kodini o'rashimiz mumkin bo'lgan joyni topsak, faqat o'sha so'rovga ko'rinadigan holatga kirish va uni o'zgartira olamiz, bu esa REQUEST scope dagi providerlar va ularning ba'zi cheklovlariga muqobil bo'lishi mumkin.
Yoki ALS dan tizimning faqat bir qismi uchun (masalan, transaction obyekti) kontekstni, uni servislarda aniq uzatmasdan, tarqatish uchun foydalanishimiz mumkin, bu izolyatsiya va inkapsulyatsiyani oshirishi mumkin.
Maxsus implementatsiya
NestJS o'zida AsyncLocalStorage uchun hech qanday o'rnatilgan abstraksiyani taqdim etmaydi, shuning uchun butun konsepsiyani yaxshiroq tushunish uchun uni eng sodda HTTP holati uchun qanday o'zimiz implementatsiya qilishimizni ko'rib chiqamiz:
Tayyor maxsus paket uchun pastroqda o'qing.
- Avval, umumiy manba faylida
AsyncLocalStoragening yangi nusxasini yarating. NestJS dan foydalanganimiz sababli, uni maxsus providerga ega modulga aylantiramiz.
1@Module({
2 providers: [
3 {
4 provide: AsyncLocalStorage,
5 useValue: new AsyncLocalStorage(),
6 },
7 ],
8 exports: [AsyncLocalStorage],
9})
10export class AlsModule {}AsyncLocalStorageasync_hooks dan import qilinadi.
- Biz faqat HTTP bilan ishlaymiz, shuning uchun
nextfunksiyasiniAsyncLocalStorage#runbilan o'rash uchun middleware dan foydalanamiz. Middleware so'rov keladigan birinchi nuqta bo'lgani uchun, bustoreni barcha enhancerlar va qolgan tizimda mavjud qiladi.
1@Module({
2 imports: [AlsModule],
3 providers: [CatsService],
4 controllers: [CatsController],
5})
6export class AppModule implements NestModule {
7 constructor(
8 // inject the AsyncLocalStorage in the module constructor,
9 private readonly als: AsyncLocalStorage
10 ) {}
11
12 configure(consumer: MiddlewareConsumer) {
13 // bind the middleware,
14 consumer
15 .apply((req, res, next) => {
16 // populate the store with some default values
17 // based on the request,
18 const store = {
19 userId: req.headers['x-user-id'],
20 };
21 // and pass the "next" function as callback
22 // to the "als.run" method together with the store.
23 this.als.run(store, () => next());
24 })
25 .forRoutes('*path');
26 }
27}- Endi so'rov hayotiy siklining istalgan joyida lokal store nusxasiga murojaat qilishimiz mumkin.
1@Injectable()
2export class CatsService {
3 constructor(
4 // We can inject the provided ALS instance.
5 private readonly als: AsyncLocalStorage,
6 private readonly catsRepository: CatsRepository,
7 ) {}
8
9 getCatForUser() {
10 // The "getStore" method will always return the
11 // store instance associated with the given request.
12 const userId = this.als.getStore()["userId"] as number;
13 return this.catsRepository.getForUser(userId);
14 }
15}- Tamom. Endi butun
REQUESTobyektini inject qilmasdan so'rov bilan bog'liq holatni almashish usuliga egamiz.
Bu usul ko'plab use-case lar uchun foydali bo'lsa-da, tabiatan kod oqimini yashiradi (implitsit kontekst yaratadi), shuning uchun undan mas'uliyat bilan foydalaning va ayniqsa kontekstual "God objects" yaratishdan saqlaning.
NestJS CLS
nestjs-cls paketi oddiy AsyncLocalStorage ishlatishga nisbatan bir nechta DX yaxshilanishlarini taqdim etadi (CLS continuation-local storage atamasining qisqartmasi). U implementatsiyani ClsModule ga abstraksiyalaydi va store ni turli transportlar (faqat HTTP emas) uchun boshlashning turli usullarini hamda qat'iy tiplashni qo'llab-quvvatlaydi.
Store ga keyin injectable ClsService yordamida kirish mumkin, yoki uni Proxy Providers orqali biznes mantiqdan to'liq abstraksiyalash mumkin.
nestjs-cls uchinchi tomon paketi bo'lib, NestJS core jamoasi tomonidan boshqarilmaydi. Kutubxona bilan bog'liq muammolarni tegishli repoda xabar bering.
O'rnatish
@nestjs kutubxonalariga peer qaramlikdan tashqari, u faqat o'rnatilgan Node.js API dan foydalanadi. Uni boshqa paketlar kabi o'rnating.
1npm i nestjs-clsFoydalanish
Yuqorida tasvirlangan o'xshash funksionallik ni nestjs-cls yordamida quyidagicha amalga oshirish mumkin:
ClsModuleni ildiz modulga import qiling.
1@Module({
2 imports: [
3 // Register the ClsModule,
4 ClsModule.forRoot({
5 middleware: {
6 // automatically mount the
7 // ClsMiddleware for all routes
8 mount: true,
9 // and use the setup method to
10 // provide default store values.
11 setup: (cls, req) => {
12 cls.set('userId', req.headers['x-user-id']);
13 },
14 },
15 }),
16 ],
17 providers: [CatsService],
18 controllers: [CatsController],
19})
20export class AppModule {}- So'ng
ClsServiceni store qiymatlariga kirish uchun ishlatish mumkin.
1@Injectable()
2export class CatsService {
3 constructor(
4 // We can inject the provided ClsService instance,
5 private readonly cls: ClsService,
6 private readonly catsRepository: CatsRepository,
7 ) {}
8
9 getCatForUser() {
10 // and use the "get" method to retrieve any stored value.
11 const userId = this.cls.get('userId');
12 return this.catsRepository.getForUser(userId);
13 }
14}ClsServiceboshqaradigan store qiymatlarining qat'iy tiplanishini olish (va string kalitlar uchun auto-suggestion ga ega bo'lish) uchun uni inject qilganda ixtiyoriyClsService<MyClsStore>tip parametridan foydalanishimiz mumkin.
1export interface MyClsStore extends ClsStore {
2 userId: number;
3}Paketning so'rov ID sini avtomatik generatsiya qilishiga ruxsat berish va keyin cls.getId() orqali olish, yoki butun Request obyektini cls.get(CLS_REQ) orqali olish ham mumkin.
Testlash
ClsService shunchaki yana bir injectable provider bo'lgani uchun, uni unit testlarda to'liq mock qilish mumkin.
Ammo ayrim integratsion testlarda haqiqiy ClsService implementatsiyasidan foydalanishni xohlashimiz mumkin. Bunday holatda kontekstga bog'liq kod qismini ClsService#run yoki ClsService#runWith chaqirig'i bilan o'rashimiz kerak bo'ladi.
1describe('CatsService', () => {
2 let service: CatsService
3 let cls: ClsService
4 const mockCatsRepository = createMock<CatsRepository>()
5
6 beforeEach(async () => {
7 const module = await Test.createTestingModule({
8 // Set up most of the testing module as we normally would.
9 providers: [
10 CatsService,
11 {
12 provide: CatsRepository
13 useValue: mockCatsRepository
14 }
15 ],
16 imports: [
17 // Import the static version of ClsModule which only provides
18 // the ClsService, but does not set up the store in any way.
19 ClsModule
20 ],
21 }).compile()
22
23 service = module.get(CatsService)
24
25 // Also retrieve the ClsService for later use.
26 cls = module.get(ClsService)
27 })
28
29 describe('getCatForUser', () => {
30 it('retrieves cat based on user id', async () => {
31 const expectedUserId = 42
32 mocksCatsRepository.getForUser.mockImplementationOnce(
33 (id) => ({ userId: id })
34 )
35
36 // Wrap the test call in the `runWith` method
37 // in which we can pass hand-crafted store values.
38 const cat = await cls.runWith(
39 { userId: expectedUserId },
40 () => service.getCatForUser()
41 )
42
43 expect(cat.userId).toEqual(expectedUserId)
44 })
45 })
46})Qo'shimcha ma'lumot
To'liq API hujjatlari va ko'proq kod namunalari uchun NestJS CLS GitHub sahifasi ga tashrif buyuring.