Skip to content

Commit d768657

Browse files
committed
Dynamic version selector using Hugo vars
1 parent 987c038 commit d768657

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+6983
-51
lines changed

config.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
baseurl = "https://www.graphql-java.com"
22
languageCode = "en-us"
33
title = ""
4-
# Copyright notice. This is displayer in the footer.
5-
copyright = "© Andreas Marek"
64
theme = "hugo-sustain"
75
pygmentsUseClasses=true
86

7+
[params]
8+
latestReleases = ["v9.4", "v10.0"]
9+
latestDocs = "v10"
10+
911
## Main Menu
1012
[[menu.main]]
1113
name = "blog"

content/documentation/_index.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
title: "Documentation"
3+
date: 2018-09-09T18:19:33+06:00
4+
---
5+
graphql-java documentation
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
---
2+
title: "Batching"
3+
date: 2018-09-09T12:52:46+10:00
4+
draft: false
5+
tags: [documentation]
6+
weight: 101
7+
description: Batching
8+
---
9+
# Using Dataloader
10+
11+
If you are using `graphql`, you are likely to making queries on a graph of data (surprise surprise). But it's easy
12+
to implement inefficient code with naive loading of a graph of data.
13+
14+
Using `java-dataloader` will help you to make this a more efficient process by both caching and batching requests for that graph of data items. If `dataloader`
15+
has seen a data item before, it will have cached the value and will return it without having to ask for it again.
16+
17+
Imagine we have the StarWars query outlined below. It asks us to find a hero and their friend's names and their friend's friend's
18+
names. It is likely that many of these people will be friends in common.
19+
20+
{{< highlight graphql "linenos=table" >}}
21+
22+
{
23+
hero {
24+
name
25+
friends {
26+
name
27+
friends {
28+
name
29+
}
30+
}
31+
}
32+
}
33+
{{< / highlight >}}
34+
35+
The result of this query is displayed below. You can see that Han, Leia, Luke and R2-D2 are a tight knit bunch of friends and
36+
share many friends in common.
37+
38+
{{< highlight json "linenos=table" >}}
39+
40+
[
41+
hero: [
42+
name: 'R2-D2',
43+
friends: [
44+
[
45+
name: 'Luke Skywalker',
46+
friends: [
47+
[name: 'Han Solo'],
48+
[name: 'Leia Organa'],
49+
[name: 'C-3PO'],
50+
[name: 'R2-D2']
51+
]
52+
],
53+
[
54+
name: 'Han Solo',
55+
friends: [
56+
[name: 'Luke Skywalker'],
57+
[name: 'Leia Organa'],
58+
[name: 'R2-D2']
59+
]
60+
],
61+
[
62+
name: 'Leia Organa',
63+
friends: [
64+
[name: 'Luke Skywalker'],
65+
[name: 'Han Solo'],
66+
[name: 'C-3PO'],
67+
[name: 'R2-D2']
68+
]
69+
]
70+
]
71+
]
72+
]
73+
{{< / highlight >}}
74+
75+
A naive implementation would call a `DataFetcher` to retrieve a person object every time it was invoked.
76+
77+
In this case it would be *15* calls over the network. Even though the group of people have a lot of common friends.
78+
With `dataloader` you can make the `graphql` query much more efficient.
79+
80+
As `graphql` descends each level of the query (e.g. as it processes `hero` and then `friends` and then for each their `friends`),
81+
the data loader is called to "promise" to deliver a person object. At each level `dataloader.dispatch()` will be
82+
called to fire off the batch requests for that part of the query. With caching turned on (the default) then
83+
any previously returned person will be returned as-is for no cost.
84+
85+
In the above example there are only *5* unique people mentioned but with caching and batching retrieval in place there will be only
86+
*3* calls to the batch loader function. *3* calls over the network or to a database is much better than *15* calls, you will agree.
87+
88+
If you use capabilities like `java.util.concurrent.CompletableFuture.supplyAsync()` then you can make it even more efficient by making the
89+
the remote calls asynchronous to the rest of the query. This will make it even more timely since multiple calls can happen at once
90+
if need be.
91+
92+
Here is how you might put this in place:
93+
94+
95+
{{< highlight java "linenos=table" >}}
96+
97+
// a batch loader function that will be called with N or more keys for batch loading
98+
BatchLoader<String, Object> characterBatchLoader = new BatchLoader<String, Object>() {
99+
@Override
100+
public CompletionStage<List<Object>> load(List<String> keys) {
101+
//
102+
// we use supplyAsync() of values here for maximum parellisation
103+
//
104+
return CompletableFuture.supplyAsync(() -> getCharacterDataViaBatchHTTPApi(keys));
105+
}
106+
};
107+
108+
// a data loader for characters that points to the character batch loader
109+
DataLoader<String, Object> characterDataLoader = new DataLoader<>(characterBatchLoader);
110+
111+
//
112+
// use this data loader in the data fetchers associated with characters and put them into
113+
// the graphql schema (not shown)
114+
//
115+
DataFetcher heroDataFetcher = new DataFetcher() {
116+
@Override
117+
public Object get(DataFetchingEnvironment environment) {
118+
return characterDataLoader.load("2001"); // R2D2
119+
}
120+
};
121+
122+
DataFetcher friendsDataFetcher = new DataFetcher() {
123+
@Override
124+
public Object get(DataFetchingEnvironment environment) {
125+
StarWarsCharacter starWarsCharacter = environment.getSource();
126+
List<String> friendIds = starWarsCharacter.getFriendIds();
127+
return characterDataLoader.loadMany(friendIds);
128+
}
129+
};
130+
131+
//
132+
// DataLoaderRegistry is a place to register all data loaders in that needs to be dispatched together
133+
// in this case there is 1 but you can have many
134+
//
135+
DataLoaderRegistry registry = new DataLoaderRegistry();
136+
registry.register("character", characterDataLoader);
137+
138+
//
139+
// this instrumentation implementation will dispatch all the dataloaders
140+
// as each level fo the graphql query is executed and hence make batched objects
141+
// available to the query and the associated DataFetchers
142+
//
143+
DataLoaderDispatcherInstrumentation dispatcherInstrumentation
144+
= new DataLoaderDispatcherInstrumentation(registry);
145+
146+
//
147+
// now build your graphql object and execute queries on it.
148+
// the data loader will be invoked via the data fetchers on the
149+
// schema fields
150+
//
151+
GraphQL graphQL = GraphQL.newGraphQL(buildSchema())
152+
.instrumentation(dispatcherInstrumentation)
153+
.build();
154+
155+
{{< / highlight >}}
156+
157+
One thing to note is the above only works if you use `DataLoaderDispatcherInstrumentation` which makes sure `dataLoader.dispatch()`
158+
is called. If this was not in place, then all the promises to data will never be dispatched ot the batch loader function
159+
and hence nothing would ever resolve.
160+
161+
## Data Loader only works with AsyncExecutionStrategy
162+
163+
The only execution that works with DataLoader is `graphql.execution.AsyncExecutionStrategy`. This is because this execution strategy knows
164+
then the most optimal time to dispatch() your load calls is. It does this by deeply tracking how many fields are outstanding and whether they
165+
are list values and so on.
166+
167+
Other execution strategies such as `ExecutorServiceExecutionStrategy` cant do this and hence if the data loader code detects
168+
you are not using `AsyncExecutionStrategy` then it will simple dispatch the data loader as each field is encountered. You
169+
may get `caching` of values but you will not get `batching` of them.
170+
171+
172+
## Per Request Data Loaders
173+
174+
If you are serving web requests then the data can be specific to the user requesting it. If you have user specific data then you will not want to
175+
cache data meant for user A to then later give it to user B in a subsequent request.
176+
177+
The scope of your DataLoader instances is important. You might want to create them per web request to
178+
ensure data is only cached within that web request and no more.
179+
180+
If your data can be shared across web requests then you might want to scope your data loaders so they survive
181+
longer than the web request say.
182+
183+
But if you are doing per request data loaders then creating a new set of `GraphQL` and `DataLoader` objects per
184+
request is super cheap. It's the `GraphQLSchema` creation that can be expensive, especially if you are using graphql SDL parsing.
185+
186+
Structure your code so that the schema is statically held, perhaps in a static variable or in a singleton IoC component but
187+
build out a new `GraphQL` set of objects on each request.
188+
189+
190+
{{< highlight java "linenos=table" >}}
191+
192+
GraphQLSchema staticSchema = staticSchema_Or_MayBeFrom_IoC_Injection();
193+
194+
DataLoaderRegistry registry = new DataLoaderRegistry();
195+
registry.register("character", getCharacterDataLoader());
196+
197+
DataLoaderDispatcherInstrumentation dispatcherInstrumentation
198+
= new DataLoaderDispatcherInstrumentation(registry);
199+
200+
GraphQL graphQL = GraphQL.newGraphQL(staticSchema)
201+
.instrumentation(dispatcherInstrumentation)
202+
.build();
203+
204+
graphQL.execute("{ helloworld }");
205+
206+
// you can now throw away the GraphQL and hence DataLoaderDispatcherInstrumentation
207+
// and DataLoaderRegistry objects since they are really cheap to build per request
208+
209+
{{< / highlight >}}
210+
211+
## Async Calls On Your Batch Loader Function Only
212+
213+
The data loader code pattern works by combining all the outstanding data loader calls into more efficient batch loading calls.
214+
215+
graphql-java tracks what outstanding data loader calls have been made and it is its responsibility to call `dispatch`
216+
in the background at the most optimal time, which is when all graphql fields have been examined and dispatched.
217+
218+
However there is a code pattern that will cause your data loader calls to never complete and these *MUST* be avoided. This bad
219+
pattern consists of making a an asynchronous off thread call to a `DataLoader` in your data fetcher.
220+
221+
The following will not work (it will never complete).
222+
223+
{{< highlight java "linenos=table" >}}
224+
225+
BatchLoader<String, Object> batchLoader = new BatchLoader<String, Object>() {
226+
@Override
227+
public CompletionStage<List<Object>> load(List<String> keys) {
228+
return CompletableFuture.completedFuture(getTheseCharacters(keys));
229+
}
230+
};
231+
232+
DataLoader<String, Object> characterDataLoader = new DataLoader<>(batchLoader);
233+
234+
DataFetcher dataFetcherThatCallsTheDataLoader = new DataFetcher() {
235+
@Override
236+
public Object get(DataFetchingEnvironment environment) {
237+
//
238+
// Don't DO THIS!
239+
//
240+
return CompletableFuture.supplyAsync(() -> {
241+
String argId = environment.getArgument("id");
242+
return characterDataLoader.load(argId);
243+
});
244+
}
245+
};
246+
{{< / highlight >}}
247+
248+
In the example above, the call to `characterDataLoader.load(argId)` can happen some time in the future on another thread. The graphql-java
249+
engine has no way of knowing when it's good time to dispatch outstanding `DataLoader` calls and hence the data loader call might never complete
250+
as expected and no results will be returned.
251+
252+
Remember a data loader call is just a promise to actually get a value later when its an optimal time for all outstanding calls to be batched
253+
together. The most optimal time is when the graphql field tree has been examined and all field values are currently dispatched.
254+
255+
The following is how you can still have asynchronous code, by placing it into the `BatchLoader` itself.
256+
257+
{{< highlight java "linenos=table" >}}
258+
259+
BatchLoader<String, Object> batchLoader = new BatchLoader<String, Object>() {
260+
@Override
261+
public CompletionStage<List<Object>> load(List<String> keys) {
262+
return CompletableFuture.supplyAsync(() -> getTheseCharacters(keys));
263+
}
264+
};
265+
266+
DataLoader<String, Object> characterDataLoader = new DataLoader<>(batchLoader);
267+
268+
DataFetcher dataFetcherThatCallsTheDataLoader = new DataFetcher() {
269+
@Override
270+
public Object get(DataFetchingEnvironment environment) {
271+
//
272+
// This is OK
273+
//
274+
String argId = environment.getArgument("id");
275+
return characterDataLoader.load(argId);
276+
}
277+
};
278+
{{< / highlight >}}
279+
280+
Notice above the `characterDataLoader.load(argId)` returns immediately. This will enqueue the call for data until a later time when all
281+
the graphql fields are dispatched.
282+
283+
Then later when the `DataLoader` is dispatched, it's `BatchLoader` function is called. This code can be asynchronous so that if you have multiple batch loader
284+
functions they all can run at once. In the code above `CompletableFuture.supplyAsync(() -> getTheseCharacters(keys));` will run the ``getTheseCharacters``
285+
method in another thread.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
title: "Concerns"
3+
date: 2018-09-09T12:52:46+10:00
4+
draft: false
5+
tags: [documentation]
6+
weight: 102
7+
description: Application concerns
8+
---
9+
# Application concerns
10+
11+
The graphql-java library concentrates on providing an engine for the execution of queries according to the specification.
12+
13+
It does not concern itself about other high level application concerns such as the following :
14+
15+
- Database access
16+
- Caching data
17+
- Data authorisation
18+
- Data pagination
19+
- HTTP transfer
20+
- JSON encoding
21+
- Code wiring via dependency injection
22+
23+
You need to push these concerns into your business logic layers.
24+
25+
The following are great links to read more about this
26+
27+
- http://graphql.org/learn/serving-over-http/
28+
- http://graphql.org/learn/authorization/
29+
- http://graphql.org/learn/pagination/
30+
- http://graphql.org/learn/caching/
31+
32+
## Context Objects
33+
34+
You can pass in a context object during query execution that will allow you to better invoke that business logic.
35+
36+
For example the edge of your application could be performing user detection and you need that information inside the
37+
graphql execution to perform authorisation.
38+
39+
This made up example shows how you can pass yourself information to help execute your queries.
40+
41+
{{< highlight java "linenos=table" >}}
42+
43+
//
44+
// this could be code that authorises the user in some way and sets up enough context
45+
// that can be used later inside data fetchers allowing them
46+
// to do their job
47+
//
48+
UserContext contextForUser = YourGraphqlContextBuilder.getContextForUser(getCurrentUser());
49+
50+
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
51+
.context(contextForUser)
52+
.build();
53+
54+
ExecutionResult executionResult = graphQL.execute(executionInput);
55+
56+
// ...
57+
//
58+
// later you are able to use this context object when a data fetcher is invoked
59+
//
60+
61+
DataFetcher dataFetcher = new DataFetcher() {
62+
@Override
63+
public Object get(DataFetchingEnvironment environment) {
64+
UserContext userCtx = environment.getContext();
65+
Long businessObjId = environment.getArgument("businessObjId");
66+
67+
return invokeBusinessLayerMethod(userCtx, businessObjId);
68+
}
69+
};
70+
71+
{{< / highlight >}}

0 commit comments

Comments
 (0)