Passer au contenu

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 :

Fenêtre de terminal
$ 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().

Configuration de CacheModule
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 :

Injection de CACHE_MANAGER
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é.

Récupérer des éléments du cache
const value = await this.cacheManager.get('key');

Pour ajouter un élément au cache, utilisez la méthode set :

Ajouter un élément au cache
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 :

Spécifier un TTL
await this.cacheManager.set('key', 'value', 1000);

Pour désactiver l’expiration du cache, définissez la propriété de configuration ttl sur 0 :

Désactiver l'expiration
await this.cacheManager.set('key', 'value', 0);

Pour supprimer un élément du cache, utilisez la méthode del :

Supprimer un élément du cache
await this.cacheManager.del('key');

Pour vider tout le cache, utilisez la méthode reset :

Vider le cache
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.

Utilisation de CacheInterceptor
@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 :

Configuration globale de CacheInterceptor
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().

Personnalisation des options de cache
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).

Déclaration de CacheModule comme global
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.

Définir un TTL personnalisé
@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.

Application de CacheInterceptor sur WebSocket
@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.

Spécifier un TTL pour WebSocket
@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().

Personnaliser le suivi avec CacheInterceptor
@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().

Configuration de Redis store
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 :

Utilisation d'une fonction de fabrication pour la configuration
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).

Injection de dépendances dans une configuration asynchrone
CacheModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
ttl: configService.get('CACHE_TTL'),
}),
inject: [ConfigService],
});

Alternativement, vous pouvez utiliser la méthode useClass :

Utilisation d'une classe pour la configuration asynchrone
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 :

Implémentation de CacheOptionsFactory
@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 :

Utilisation d'un fournisseur de configuration existant
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.