LoginSignup
4
3

More than 3 years have passed since last update.

nest.jsのGraphQLで楽にキャッシュする

Last updated at Posted at 2019-05-22

おそらくGraphQLにおいてキャッシュの仕組みを導入するならDataLoaderが最適解だとは思うのですが、nest.jsでDalaloaderを使用する方法が確立されていません。

※ 2020/01/20 追記
@nestjs/graphql でdataloaderを使う方法を書きました

参考issue:
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を使用せず自前でキャッシュの仕組みを実装します。

CacheMap

Custom Decoratorの機能を使って、contextにmapを保持するようにします。contextにデータを保持するので、データは1リクエストの間だけ保持されます。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;
  }
}

余談1

CacheModuleのようにInterceptorを使ったキャッシュもできますが、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);
  }
}

余談2

エイリアスを使った同じクエリに対してキャッシュは有効でないのでやっぱりDataLoaderほしい...

query getUsers {
  admins: users(role: admin) {
    id
    firstName
    lastName
    phone
    username
  }
  accountants: users(role: accountant) {
    id
    firstName
    lastName
    phone
    username
  }
}
4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3