typescript - Vreau să implementez caching personalizat în nestjs


Mecanismul implicit de caching din nsetjs nu oferă suficientă flexibilitate, deoarece nu poți să adnotezi rutele/metodele individuale cu directiva @Cache sau ceva de genul acesta.

Vreau să pot seta un TTL personalizat, precum și să nu cachez fiecare rută. Poate chiar are sens să mut cache-ul la nivel de serviciu pentru acest scop, încă nu sunt sigur.

Mă întrebam cum ai face asta într-un mod mai bun în cadrul framework-ului NestJS. Doar pentru a cache-ui anumite rute sau metode ale serviciului.

Răspuns #1

Am început recent să lucrez la un modul de caching pentru NestJS după ce m-am confruntat cu aceleași probleme ca și tine. Este disponibil pe npm la @nestjs-plus/caching, deși nu este încă gata de utilizare, îți voi împărtăși definiția Interceptorului aici. Se bazează pe pattern-ul mixin pentru a putea primi opțiuni pe fiecare rută.

import { makeInjectableMixin } from '@nestjs-plus/common';
import {
  ExecutionContext,
  Inject,
  Injectable,
  NestInterceptor
} from '@nestjs/common';
import { forkJoin, Observable, of } from 'rxjs';
import { catchError, map, switchMap } from 'rxjs/operators';
import { Cache, CacheToken } from './cache';

@Injectable()
export abstract class CachingInterceptor implements NestInterceptor {
  protected abstract readonly options: CacheOptions;

  constructor(@Inject(CacheToken) private readonly cache: Cache) {}

  async intercept(
    context: ExecutionContext,
    call$: Observable<any>
  ): Promise<Observable<any>> {
    const http = context.switchToHttp();
    const request = http.getRequest();
    const key = this.options.getKey(request);

    const cached = await this.cache.get(key);
    if (cached != null) {
      return of(cached);
    }

    return call$.pipe(
      switchMap(result => {
        return forkJoin(
          of(result),
          this.cache.set(key, result, this.options.ttl)
        ).pipe(catchError(e => of(result)));
      }),
      map(([result, setOp]) => result)
    );
  }
}

export interface CacheOptions {
  ttl: number;
  getKey: (request) => string;
}

export const makeCacheInterceptor = (options: CacheOptions) => {
  return makeInjectableMixin('CachingInterceptor')(
    class extends CachingInterceptor {
      protected readonly options = options;
    }
  );
};

export interface Cache {
  get: (key: string) => Promise<any | null | undefined>;
  set: (key: string, data: any, ttl: number) => Promise<void>;
  del: (key: string) => Promise<void>;
}

export const CacheToken = Symbol('CacheToken');

Acest model permite aplicarea caching-ului pe fiecare rută în controllerele tale cu TTL-uri diferite sau metode de extragere a cheii de cache dintr-o cerere primită.

@Get()
  @UseInterceptors(
    makeCacheInterceptor({
      getKey: () => '42' // could be req url, query params, etc,
      ttl: 5,
    }),
  )
  getHello(): string {
    return this.appService.getHello();
  }

Singurul lucru care lipsește de aici (și din biblioteca la care lucrez) este un set flexibil de implementări ale cache-ului, cum ar fi în memorie, redis, db etc. Plănuiesc să integrez biblioteca cache-manager pentru a acoperi acest gol săptămâna aceasta (este același furnizor de cache pe care-l folosește Nest pentru implementarea standard a Cache-ului). Simțiți-vă liber să folosiți aceasta ca bază pentru a vă crea propriul cache sau rămâneți la curent pentru când @nestjs-plus/caching va fi gata de utilizare. Voi actualiza această întrebare mai târziu săptămâna aceasta, când voi publica o versiune gata pentru producție.

Răspunsul #2

Acum este posibil să setați TTL (Timp de expirare) și CacheKey la nivel de rută în NestJS.

Consultați secțiunea despre suprascrierile cache-ului global în documentația NestJS despre caching.

Un fragment de cod care demonstrează decoratoarele @CacheKey și @CacheTTL care oferă această funcționalitate:

@Controller()
@UseInterceptors(CacheInterceptor)
export class AppController {

  @Get()
  @CacheKey('custom_key')
  @CacheTTL(20)
  findAll(): string[] {
    return [];
  }
}