在Nest.js的GraphQL中轻松实现缓存
我认为在GraphQL中引入缓存机制的最佳选择可能是使用DataLoader,但是在nest.js中尚未建立Dalaloader的使用方法。
※ 2020/01/20 追加
我写了一篇关于如何在 @nestjs/graphql 中使用dataloader的方法。
参考问题:
https://github.com/nestjs/graphql/issues/20
https://github.com/nestjs/graphql/issues/2
除了DataLoader之外,nest.js还提供了使用cache-manager的CacheModule。不过,缺少在GraphQL中使用的示例,并且在我的环境中也无法正常运行,所以看起来不受支持。
https://docs.nestjs.com/techniques/caching
目前,在nest.js中,缓存机制尚未完全建立起来,并且我认为未来可能会发生重大变化,因此我计划不使用DataLoader或cache-manager,而是自行实现缓存机制。
缓存地图
使用Custom Decorator的功能,将map保存在context中。由于数据保存在context中,因此数据将仅在一个请求的期间内保持。就像DataLoader一样。
https://docs.nestjs.com/graphql/tooling#custom-decorators
export const CacheMap = createParamDecorator(
(key: string = 'default', [root, args, ctx, info]) => {
if (!ctx.cacheMap) {
ctx.cacheMap = {} as { [key: string]: Map<string, any> };
}
if (!ctx.cacheMap[key]) {
ctx.cacheMap[key] = new Map<string, any>();
}
return ctx.cacheMap[key];
},
);
使用方法
使用方法非常简单。通过这种方式创建了一个简单的缓存机制。
@Resolver('Query')
export class QueryResolver {
constructor(private readonly service: Service) {}
@ResolveProperty()
public async users(@CacheMap() cache: Map<string, User[]>): Promise<User[]> {
const cacheKey = `users`;
const users = cache.has(cacheKey)
? cache.get(cacheKey)!
: await this.service.find();
cache.set(cacheKey, users);
return users;
}
}
我們要討論的第一個話題是
我们可以像CacheModule一样使用拦截器进行缓存,但由于GraphQL通常在查询中使用relay、filter、orderBy等进行处理,因此决定对返回值进行缓存会被视为无效的。
@Resolver('Query')
export class QueryResolver {
constructor(private readonly service: Service) {}
@ResolveProperty()
public async users(
@ConnectionArgs() connectionArgs: ConnectionArguments,
@CacheMap() cache: Map<string, User[]>): Promise<User[]> {
const cacheKey = `users`;
const users = cache.has(cacheKey)
? cache.get(cacheKey)!
: await this.service.find();
cache.set(cacheKey, users);
// 実際はここで更に加工
// https://www.npmjs.com/package/graphql-relay の connectionFromArray を使ったりしてる
return connectionFromArray(users, connectionArgs);
}
}
另外谈谈第二点
由于使用别名的相同查询不会启用缓存,所以我还是希望有一个DataLoader…
query getUsers {
admins: users(role: admin) {
id
firstName
lastName
phone
username
}
accountants: users(role: accountant) {
id
firstName
lastName
phone
username
}
}