🐙
NestJS+Fastify+MercuriusにdataLoaderを入れる
graphqlのN+1問題対応で、DataLoaderを入れ時に、少しハマってしまったので、書き残しておきます。
DataLoaderについての詳しい説明は割愛します。
graphql/dataloader はアプリケーションのデータ取得に使用される汎用的なユーティリティであり、データソースからのデータ取得を Batch 処理したり結果を Cache するための簡単な API を提供する。 これにより、データ取得リクエストを大幅に効率化することができる
-- by Google Bard
graphq schema👇
graphql.schema
Query {
feeds(): Feeds
}
type Feeds {
id: String!
title: String!
images(take: Int): [FeedImage!]!
}
resolver👇
feed.resolver.ts
@Resolver('Feeds')
export class FeedResolver {
@Query(() => Object, {
name: 'feeds',
nullable: false
})
async feeds(){
// feeds取得するロジック
}
@ResolveField('images')
async feedImages(
@Parent() feed: Feed
) {
// ここは後程dataLoaderの呼び出しに変えます
return null;
}
}
dataloaderをインストールします。
yarn add dataloader
moduleファイル👇
app.module.ts
// ファイル分けずにここに書いちゃいます。
@Injectable(
/**
* ここにscopeでrequestの指定ができますが
* それを指定するとGraphqlModuleがネストが深くなるせいで初期化されないみたいです
* それで結構ハマりました
**/
)
class DataLoaders {
constuctor(
@Inject() feedService: FeedService
){}
getAllLoaders() {
return {
feedImage: new DataLoader<string, FeedImage>((feedIds) => {
const images = this.feedService.getFeedImages(feedIds);
return feedIds.map(id => images.filter(img => img.feedId === id));
})
}
}
}
@Module({
exports: [DataLoaders]
provider: [DataLoaders]
})
class DataLoaderModule{}
@Module({
imports: [
GraphqlModule.forRootAsync({
driver: MercuriusDriver
useFactory: (dataLoaders: DataLoaders) => ({
// ほかのオプションは省略
context: (req: FastifyRequest, reply: FastifyReply) => ({
// graphqlでのHTTP contextの中にloaderを含んでしまいます。
req, reply, dataLoaders: dataLoaders.getAllLoaders()
})
}),
imports: [DataLoaderModule],
inject: [DataLoaders]
})
]
})
export class AppModule {}
resolverの修正👇
feed.resolver.ts
@Resolver('Feeds')
export class FeedResolver {
@Query(() => Object, {
name: 'feeds',
nullable: false
})
async feeds(){
// feeds取得するロジック
}
@ResolveField('images')
async feedImages(
@Parent() feed: Feed,
@Context() context: { dataLoaders: any // <- ここはちゃんとtypeを定義したほうがいいです。 }
) {
- // ここは後程dataLoaderの呼び出しに変えます
- return null;
+ return await context.dataLoaders.feedImage.load(feed.id);
}
}
で以上です。
importは省略しました🙇♂️
もう少し賢い書き方もありそうですが、とりあえずこれで動かすことができたので、一旦これでいきます。
Discussion