Mise en cache
La mise en cache est une technique simple et efficace qui améliore les performances de votre application. Elle agit comme un stockage temporaire de données, fournissant un accès rapide aux données.
Installation
Tout d’abord, installez les packages requis :
$ npm install @nestjs/cache-manager cache-manager
Cache en mémoire
Nest propose une API unifiée pour divers fournisseurs de stockage de cache. Le stockage intégré est une mémoire de données. Cependant, vous pouvez facilement passer à une solution plus complète, comme Redis.
Pour activer la mise en cache, importez le CacheModule
et appelez sa méthode register()
.
import { Module } from '@nestjs/common';import { CacheModule } from '@nestjs/cache-manager';import { AppController } from './app.controller';
@Module({ imports: [CacheModule.register()], controllers: [AppController],})export class AppModule {}
Interaction avec le magasin de cache
Pour interagir avec l’instance cache-manager
, injectez-la dans votre classe en utilisant le token CACHE_MANAGER
, comme suit :
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
La méthode get
sur l’instance Cache
(du package cache-manager
) est utilisée pour récupérer des éléments du cache. Si l’élément n’existe pas dans le cache, null
sera retourné.
const value = await this.cacheManager.get('key');
Pour ajouter un élément au cache, utilisez la méthode set
:
await this.cacheManager.set('key', 'value');
Le stockage de cache en mémoire ne peut stocker que des valeurs de types qui sont pris en charge par l’algorithme de clonage structuré.
Le temps d’expiration par défaut du cache est de 5 secondes.
Vous pouvez spécifier manuellement un TTL (temps d’expiration en secondes) pour cette clé spécifique, comme suit :
await this.cacheManager.set('key', 'value', 1000);
Pour désactiver l’expiration du cache, définissez la propriété de configuration ttl
sur 0
:
await this.cacheManager.set('key', 'value', 0);
Pour supprimer un élément du cache, utilisez la méthode del
:
await this.cacheManager.del('key');
Pour vider tout le cache, utilisez la méthode reset
:
await this.cacheManager.reset();
Mise en cache automatique des réponses
Dans les applications GraphQL, les intercepteurs sont exécutés séparément pour chaque résolveur de champ. Ainsi, CacheModule
(qui utilise des intercepteurs pour mettre en cache les réponses) ne fonctionnera pas correctement.
Pour activer la mise en cache automatique des réponses, associez simplement le CacheInterceptor
là où vous souhaitez mettre en cache les données.
@Controller()@UseInterceptors(CacheInterceptor)export class AppController { @Get() findAll(): string[] { return []; }}
Seuls les points de terminaison GET
sont mis en cache. De plus, les routes HTTP du serveur qui injectent l’objet réponse natif (@Res()
) ne peuvent pas utiliser le Cache Interceptor. Consultez le mapping des réponses pour plus de détails.
Pour réduire la quantité de code requise, vous pouvez lier le CacheInterceptor
à tous les points de terminaison globalement :
import { Module } from '@nestjs/common';import { CacheModule, CacheInterceptor } from '@nestjs/cache-manager';import { AppController } from './app.controller';import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({ imports: [CacheModule.register()], controllers: [AppController], providers: [ { provide: APP_INTERCEPTOR, useClass: CacheInterceptor, }, ],})export class AppModule {}
Personnaliser la mise en cache
Toutes les données mises en cache ont leur propre temps d’expiration (TTL
). Pour personnaliser les valeurs par défaut, passez l’objet options à la méthode register()
.
CacheModule.register({ ttl: 5, // secondes max: 10, // nombre maximum d'éléments dans le cache});
Utiliser le module globalement
Lorsque vous souhaitez utiliser CacheModule
dans d’autres modules, vous devez l’importer (comme c’est standard avec tout module Nest). Sinon, déclarez-le comme un module global en définissant la propriété isGlobal
de l’objet options sur true
, comme ci-dessous. Dans ce cas, vous ne devrez pas importer CacheModule
dans d’autres modules une fois qu’il a été chargé dans le module racine (par exemple, AppModule
).
CacheModule.register({ isGlobal: true,});
Remplacements de cache globaux
Lorsqu’un cache global est activé, les entrées de cache sont stockées sous un CacheKey
qui est généré automatiquement en fonction du chemin de la route. Vous pouvez remplacer certains paramètres de cache (@CacheKey()
et @CacheTTL()
) sur une base par méthode, permettant des stratégies de mise en cache personnalisées pour des méthodes de contrôleur individuelles. Cela peut être le plus pertinent lors de l’utilisation de différents magasins de cache.
Vous pouvez appliquer le décorateur @CacheTTL()
sur une base par contrôleur pour définir un TTL de mise en cache pour l’ensemble du contrôleur. Dans les situations où les paramètres de TTL de cache au niveau du contrôleur et de la méthode sont définis, les paramètres de TTL de cache spécifiés au niveau de la méthode prévaudront sur ceux définis au niveau du contrôleur.
@Controller()@CacheTTL(50)export class AppController { @CacheKey('custom_key') @CacheTTL(20) findAll(): string[] { return []; }}
Le décorateur @CacheKey()
peut être utilisé avec ou sans un décorateur @CacheTTL()
correspondant et vice versa. On peut choisir de remplacer uniquement @CacheKey()
ou uniquement @CacheTTL()
. Les paramètres qui ne sont pas remplacés par un décorateur utiliseront les valeurs par défaut telles que définies globalement (voir personnalisation de mise en cache).
WebSockets et Microservices
Vous pouvez également appliquer le CacheInterceptor
aux abonnés WebSocket ainsi qu’aux modèles de microservices, indépendamment de la méthode de transport utilisée.
@CacheKey('events')@UseInterceptors(CacheInterceptor)@SubscribeMessage('events')handleEvent(client: Client, data: string[]): Observable<string[]> { return [];}
Cependant, le décorateur supplémentaire @CacheKey()
est requis pour spécifier une clé utilisée pour stocker et récupérer ultérieurement les données mises en cache. De plus, veuillez noter que vous ne devez pas tout mettre en cache. Les actions qui effectuent des opérations commerciales plutôt que de simplement interroger les données ne doivent jamais être mises en cache.
Vous pouvez également spécifier un temps d’expiration du cache (TTL) en utilisant le décorateur @CacheTTL()
, qui remplacera la valeur de TTL par défaut globale.
@CacheTTL(10)@UseInterceptors(CacheInterceptor)@SubscribeMessage('events')handleEvent(client: Client, data: string[]): Observable<string[]> { return [];}
Ajuster le suivi
Par défaut, Nest utilise l’URL de la requête (dans une application HTTP) ou la clé du cache (dans les applications WebSocket et microservices, définie via le décorateur @CacheKey()
) pour associer les enregistrements de cache à vos points de terminaison. Néanmoins, parfois vous pouvez vouloir mettre en place un suivi basé sur des facteurs différents, par exemple, l’utilisation des en-têtes HTTP (par exemple, Authorization
pour identifier correctement les points de terminaison profile
).
Pour cela, créez une sous-classe de CacheInterceptor
et remplacez la méthode trackBy()
.
@Injectable()class HttpCacheInterceptor extends CacheInterceptor { trackBy(context: ExecutionContext): string | undefined { return 'key'; }}
Différents magasins
Ce service tire parti de cache-manager en coulisses. Le package cache-manager
prend en charge une large gamme de magasins utiles, par exemple, Redis store. Une liste complète des magasins pris en charge est disponible ici. Pour configurer le magasin Redis, il suffit de passer le package avec les options correspondantes à la méthode register()
.
import { RedisClientOptions } from 'redis';import * as redisStore from 'cache-manager-redis-store';import { Module } from '@nestjs/common';import { CacheModule } from '@nestjs/cache-manager';import { AppController } from './app.controller';
@Module({ imports: [ CacheModule.register<RedisClientOptions>({ store: redisStore, // Configuration spécifique au magasin : host: 'localhost', port: 6379, }), ], controllers: [AppController],})export class AppModule {}
Configuration asynchrone
Vous pouvez vouloir passer des options de module de manière asynchrone au lieu de les passer statiquement à la compilation. Dans ce cas, utilisez la méthode registerAsync()
, qui fournit plusieurs moyens de traiter la configuration asynchrone.
Une approche consiste à utiliser une fonction de fabrication :
CacheModule.registerAsync({ useFactory: () => ({ ttl: 5, }),});
Notre fabrique se comporte comme toutes les autres usines de modules asynchrones (elle peut être async
et est capable d’injecter des dépendances via inject
).
CacheModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ ttl: configService.get('CACHE_TTL'), }), inject: [ConfigService],});
Alternativement, vous pouvez utiliser la méthode useClass
:
CacheModule.registerAsync({ useClass: CacheConfigService,});
La construction ci-dessus instanciera CacheConfigService
à l’intérieur de CacheModule
et l’utilisera pour obtenir l’objet options. CacheConfigService
doit implémenter l’interface CacheOptionsFactory
afin de fournir les options de configuration :
@Injectable()class CacheConfigService implements CacheOptionsFactory { createCacheOptions(): CacheModuleOptions { return { ttl: 5, }; }}
Si vous souhaitez utiliser un fournisseur de configuration existant importé d’un autre module, utilisez la syntaxe useExisting
:
CacheModule.registerAsync({ imports: [ConfigModule], useExisting: ConfigService,});
Cela fonctionne de la même manière que useClass
avec une différence cruciale - CacheModule
cherchera dans les modules importés pour réutiliser tout ConfigService
déjà créé, au lieu d’instancier le sien.
Exemple
Un exemple fonctionnel est disponible ici.