在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
  }
}
广告
将在 10 秒后关闭
bannerAds