原文:http://blog.mygraphql.com/wordpress/?p=110
使用 Dataloader
使用 graphql, 你很可能會去查詢圖結構的數(shù)據(jù)(graph of data )
(這可能是句廢話).
如果用簡單直接的方法去獲取每個field的數(shù)據(jù),可能會效率很低。
使用 java-dataloader 可以幫助你更有效地緩存和批量化數(shù)據(jù)加載操作。
>><<dataloader會緩存所有加載過的數(shù)據(jù),使再次使用相同數(shù)據(jù)時,不需要再加載。
假設我們用以下的 StarWars 查詢。這查詢了一個英雄(
hero)和他朋友的名字,和他朋友的朋友的名字。很多時候,他們有共同的朋友。
{
hero {
name
friends {
name
friends {
name
}
}
}
}
下面是查詢的結果。你可以看到,Han, Leia, Luke 和 R2-D2
是一群緊密的朋友。他們有很多共同的朋友。
[hero: [name: 'R2-D2', friends: [
[name: 'Luke Skywalker', friends: [
[name: 'Han Solo'], [name: 'Leia Organa'], [name: 'C-3PO'], [name: 'R2-D2']]],
[name: 'Han Solo', friends: [
[name: 'Luke Skywalker'], [name: 'Leia Organa'], [name: 'R2-D2']]],
[name: 'Leia Organa', friends: [
[name: 'Luke Skywalker'], [name: 'Han Solo'], [name: 'C-3PO'], [name: 'R2-D2']]]]]
]
一個直接的實現(xiàn)是為每個人物對象(person object)調(diào)用一次 DataFetcher
去獲取數(shù)據(jù)。
這樣你需要 15 次網(wǎng)絡調(diào)用 。即使這群有有很多相同的朋友。使用
dataloader 可以讓 graphql 查詢更高效。
因 graphql 會批量化每個層級的查詢。 ( 如先是 hero 之后是 friends
之后是他們的 friends), Data loader 返回了一個 ”
期約(promise)”,期約會返回一個 person
object.(人物對象)。在查詢的每個層級, dataloader.dispatch()
方法均會被調(diào)用一次,以獲取真實的數(shù)據(jù)。當開啟了緩存功能時
(默認開啟),將直接返回之前加載過的 person,而不會再發(fā)起一次查詢。
上例中,共有 5 個獨立的 people。但當緩存和批量化開啟后,只發(fā)起了 3
次調(diào)用 batch loader 方法的查詢操作。*3* 次網(wǎng)絡或DB訪問,當然比 15
次牛B多了。【譯者補】
如果你使用了如 java.util.concurrent.CompletableFuture.supplyAsync()
的異步程序方式 。多個field的遠程加載數(shù)據(jù)就可以并發(fā)進行了。.
這可以讓查詢更快,因一次并發(fā)了多個遠程調(diào)用。
下面就是示例代碼:
// a batch loader function that will be called with N or more keys for batch loading
BatchLoader<String, Object> characterBatchLoader = new BatchLoader<String, Object>() {
@Override
public CompletionStage<List<Object>> load(List<String> keys) {
//
// we use supplyAsync() of values here for maximum parellisation
//
return CompletableFuture.supplyAsync(() -> getCharacterDataViaBatchHTTPApi(keys));
}
};
// a data loader for characters that points to the character batch loader
DataLoader<String, Object> characterDataLoader = new DataLoader<>(characterBatchLoader);
//
// use this data loader in the data fetchers associated with characters and put them into
// the graphql schema (not shown)
//
DataFetcher heroDataFetcher = new DataFetcher() {
@Override
public Object get(DataFetchingEnvironment environment) {
return characterDataLoader.load("2001"); // R2D2
}
};
DataFetcher friendsDataFetcher = new DataFetcher() {
@Override
public Object get(DataFetchingEnvironment environment) {
StarWarsCharacter starWarsCharacter = environment.getSource();
List<String> friendIds = starWarsCharacter.getFriendIds();
return characterDataLoader.loadMany(friendIds);
}
};
//
// DataLoaderRegistry is a place to register all data loaders in that needs to be dispatched together
// in this case there is 1 but you can have many
//
DataLoaderRegistry registry = new DataLoaderRegistry();
registry.register("character", characterDataLoader);
//
// this instrumentation implementation will dispatch all the dataloaders
// as each level fo the graphql query is executed and hence make batched objects
// available to the query and the associated DataFetchers
//
DataLoaderDispatcherInstrumentation dispatcherInstrumentation
= new DataLoaderDispatcherInstrumentation(registry);
//
// now build your graphql object and execute queries on it.
// the data loader will be invoked via the data fetchers on the
// schema fields
//
GraphQL graphQL = GraphQL.newGraphQL(buildSchema())
.instrumentation(dispatcherInstrumentation)
.build();
需要注意的是,只有你使用了 DataLoaderDispatcherInstrumentation
,上面說的才會生效。由它來調(diào)用 dataLoader.dispatch() 。不然,期約(
promises ) 將不會被執(zhí)行,就更不會有數(shù)據(jù)獲取了。
查詢范圍的 Data Loaders
對于 Web
請求,請求的結果可能會因不同用戶而不同的。如果是特定用戶的數(shù)據(jù),你一定不希望用戶A的數(shù)據(jù),被用戶B查詢到。
所以 DataLoader 實例的范圍很重要。這時,你需要對每個 Request
創(chuàng)建一個新的 DataLoader,來保證它只在當前請求中生效。
如果你需要的是不同請求間共享數(shù)據(jù),所以你會希望 DataLoader
的生命周期更長。
但如用你用請求級的 data loaders ,為每個請求創(chuàng)建 GraphQL and
DataLoader 是花費很少資源的。Its the GraphQLSchema creation that can
be expensive, especially if you are using graphql SDL parsing.
i在代碼中靜態(tài)引用 schema ??梢允庆o態(tài)變量或 IoC
單件組件。但每次處理請求時,都需要創(chuàng)建 GraphQL 對象。
GraphQLSchema staticSchema = staticSchema_Or_MayBeFrom_IoC_Injection();
DataLoaderRegistry registry = new DataLoaderRegistry();
registry.register("character", getCharacterDataLoader());
DataLoaderDispatcherInstrumentation dispatcherInstrumentation
= new DataLoaderDispatcherInstrumentation(registry);
GraphQL graphQL = GraphQL.newGraphQL(staticSchema)
.instrumentation(dispatcherInstrumentation)
.build();
graphQL.execute("{ helloworld }");
// you can now throw away the GraphQL and hence DataLoaderDispatcherInstrumentation
// and DataLoaderRegistry objects since they are really cheap to build per request