DataLoader
概要
DataLoader is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching.
アプリケーションの データフェッチ層 の一部を担い、効率的なデータの取得を目的としたライブラリ Batch と Cache という2 つの機能を提供する
インストール
code:sh
$ npm install dataloader
Batching
非同期かつ一括で行うデータアクセス
より具体的には、データを取得するためのデータソース(DB)へのアクセスを一定期間待機(Lazy load) し、その間に行われたリクエストをまとめて処理する ための機構 code:sql
SELECT * FROM author
SELECT * FROM post WHERE author id IN (1,2,3})
SELECT * FROM tag WHERE post_id IN (1,2,3,4)
実装するには、Batch Function と呼ばれる関数を定義し、DataLoader コンストラクタに渡す
Batch Function
データ取得の際の key の配列を受け取り、それ対応する value の配列または Error を返す
以下の 2 つのルールを守る必要がある
1. key の配列と value の配列の長さが等しいこと
2. key の配列の順序と value の配列の順序が等しいこと
サンプルコード
code:ts
class PostDataSource {
private loader = new DataLoader(async (keys: number[]) => {
const posts = ...;
const results = posts.filter((post) => keys.includes(post.author_id));
return keys.map(key: number) => results.filter(tag => tag.post_id === key);
});
}
Batch Function への key の登録は loader.load で行う
load の戻り値は key に対応する value の Promise
複数件の取得を行うには loader.loadMany を用いる
code:ts
class PostDataSource {
...
async getPostsBy(author_id: name) {
return this.loader.load(author_id)
}
}
Caching
これにより、同一 DataLoader インスタンスから実行された Batch 内 で、同一 key を用いて複数回 value を取得するとき、メモ化 された Key-Value Store から値を返す パフォーマンス向上や DB などのデータソースへの負荷軽減が期待できる
warning.icon DataLoader のインスタンスはリクエストごとに生成すべき
リクエストごとに生成することで、以下を防げる
Cache がメモ化する Key-Value のペアがユーザやコンテキストの異なるリクエストから参照されること
メモリに書き込まれ続け大量のメモリを消化する
これにより、DataLoader インスタンスの TTL を単一のリクエストに限定できる code:ts
import DataLoader from 'dataloader';
interface ContextValue {
dataSources: {
posts: PostsDataSource;
tags: TagsDataSource;
};
}
const resolvers = {
Query: {
authors: () => getAuthors(),
},
Author: {
posts: (parent, _, { dataSources }) => dataSources.posts.getPostsBy(parent.id),
},
Post: {
tags: (parent, _, { dataSources }) => dataSources.tags.getTagsBy(parent.id),
},
};
const schemaWithResolvers = addResolversToSchema({ schema, resolvers });
const apolloServer = new ApolloServer<ContextValue>({ schema: schemaWithResolvers });
const { url } = await startStandaloneServer(apolloServer, {
context: async () => {
return {
dataSources: {
posts: new PostsDataSource(),
tags: new TagsDataSource(),
},
};
},
});