Skip to content

Commit 25d72d9

Browse files
docs
1 parent 7b51fdf commit 25d72d9

File tree

4 files changed

+1005
-0
lines changed

4 files changed

+1005
-0
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
title: "Data mapping"
3+
date: 2018-09-09T12:52:46+10:00
4+
draft: false
5+
tags: [documentation]
6+
weight: 106
7+
description: Data mapping
8+
---
9+
# Mapping data
10+
11+
## How graphql maps object data to types
12+
13+
At its heart graphql is all about declaring a type schema and mapping that over backing runtime data.
14+
15+
As the designer of the type schema, it is your challenge to get these elements to meet in the middle.
16+
17+
For example imagine we want to have a graphql type schema as follows:
18+
19+
20+
{{< highlight graphql "linenos=table" >}}
21+
type Query {
22+
products(match : String) : [Product] # a list of products
23+
}
24+
25+
type Product {
26+
id : ID
27+
name : String
28+
description : String
29+
cost : Float
30+
tax : Float
31+
}
32+
33+
{{< / highlight >}}
34+
35+
We could then run queries over this simple schema via a something like the following:
36+
37+
{{< highlight graphql "linenos=table" >}}
38+
query ProductQuery {
39+
products(match : "Paper*")
40+
{
41+
id, name, cost, tax
42+
}
43+
}
44+
45+
{{< / highlight >}}
46+
47+
We will have a ``DataFetcher`` on the ``Query.products`` field that is responsible for finding a list of products that match
48+
the argument passed in.
49+
50+
Now imagine we have 3 downstream services. One that gets product information, one that gets product cost information and one that calculates
51+
product tax information.
52+
53+
graphql-java works by running data fetchers over objects for all that information and mapping that back to the types specified in the schema.
54+
55+
Our challenge is to take these 3 sources of information and present them as one unified type.
56+
57+
We could specify data fetchers on the ``cost`` and ``tax`` fields that does those calculations but this is more to maintain and likely to lead to
58+
`N+1 performance problems`.
59+
60+
We would be better to do all this work in the ``Query.products`` data fetcher and create a unified view of the data at that point.
61+
62+
{{< highlight java "linenos=table" >}}
63+
DataFetcher productsDataFetcher = new DataFetcher() {
64+
@Override
65+
public Object get(DataFetchingEnvironment env) {
66+
String matchArg = env.getArgument("match");
67+
68+
List<ProductInfo> productInfo = getMatchingProducts(matchArg);
69+
70+
List<ProductCostInfo> productCostInfo = getProductCosts(productInfo);
71+
72+
List<ProductTaxInfo> productTaxInfo = getProductTax(productInfo);
73+
74+
return mapDataTogether(productInfo, productCostInfo, productTaxInfo);
75+
}
76+
};
77+
{{< / highlight >}}
78+
79+
So looking at the code above we have 3 types of information that need to be combined in a way such that a graphql query above can get access to
80+
the fields ``id, name, cost, tax``
81+
82+
We have two ways to create this mapping. One is via using a not type safe ``List<Map>`` structure and one by creating a type safe ``List<ProductDTO>`` class that
83+
encapsulates this unified data.
84+
85+
The ``Map`` technique could look like this.
86+
87+
{{< highlight java "linenos=table" >}}
88+
private List<Map> mapDataTogetherViaMap(List<ProductInfo> productInfo, List<ProductCostInfo> productCostInfo, List<ProductTaxInfo> productTaxInfo) {
89+
List<Map> unifiedView = new ArrayList<>();
90+
for (int i = 0; i < productInfo.size(); i++) {
91+
ProductInfo info = productInfo.get(i);
92+
ProductCostInfo cost = productCostInfo.get(i);
93+
ProductTaxInfo tax = productTaxInfo.get(i);
94+
95+
Map<String, Object> objectMap = new HashMap<>();
96+
objectMap.put("id", info.getId());
97+
objectMap.put("name", info.getName());
98+
objectMap.put("description", info.getDescription());
99+
objectMap.put("cost", cost.getCost());
100+
objectMap.put("tax", tax.getTax());
101+
102+
unifiedView.add(objectMap);
103+
}
104+
return unifiedView;
105+
}
106+
107+
{{< / highlight >}}
108+
109+
The more type safe ``DTO`` technique could look like this.
110+
111+
{{< highlight java "linenos=table" >}}
112+
113+
class ProductDTO {
114+
private final String id;
115+
private final String name;
116+
private final String description;
117+
private final Float cost;
118+
private final Float tax;
119+
120+
public ProductDTO(String id, String name, String description, Float cost, Float tax) {
121+
this.id = id;
122+
this.name = name;
123+
this.description = description;
124+
this.cost = cost;
125+
this.tax = tax;
126+
}
127+
128+
public String getId() {
129+
return id;
130+
}
131+
132+
public String getName() {
133+
return name;
134+
}
135+
136+
public String getDescription() {
137+
return description;
138+
}
139+
140+
public Float getCost() {
141+
return cost;
142+
}
143+
144+
public Float getTax() {
145+
return tax;
146+
}
147+
}
148+
149+
private List<ProductDTO> mapDataTogetherViaDTO(List<ProductInfo> productInfo, List<ProductCostInfo> productCostInfo, List<ProductTaxInfo> productTaxInfo) {
150+
List<ProductDTO> unifiedView = new ArrayList<>();
151+
for (int i = 0; i < productInfo.size(); i++) {
152+
ProductInfo info = productInfo.get(i);
153+
ProductCostInfo cost = productCostInfo.get(i);
154+
ProductTaxInfo tax = productTaxInfo.get(i);
155+
156+
ProductDTO productDTO = new ProductDTO(
157+
info.getId(),
158+
info.getName(),
159+
info.getDescription(),
160+
cost.getCost(),
161+
tax.getTax()
162+
);
163+
unifiedView.add(productDTO);
164+
}
165+
return unifiedView;
166+
}
167+
{{< / highlight >}}
168+
169+
The graphql engine will now use that list of objects and run the query sub fields ``id, name, cost, tax`` over it.
170+
171+
The default data fetcher in graphql-java is ``graphql.schema.PropertyDataFetcher`` which has both map support and POJO support.
172+
173+
For every object in the list it will look for an ``id`` field, find it by name in a map or via a `getId()` getter method and that will be sent back in the graphql
174+
response. It does that for every field in the query on that type.
175+
176+
By creating a "unified view" at the higher level data fetcher, you have mapped between your runtime view of the data and the graphql schema view of the data.
177+

content/documentation/v9/defer.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
title: "Defer"
3+
date: 2018-09-09T12:52:46+10:00
4+
draft: false
5+
tags: [documentation]
6+
weight: 107
7+
description: Defer
8+
---
9+
# Deferred Execution
10+
11+
Often when executing a query you have two classes of data. The data you need immediately and the data that could arrive little bit later.
12+
13+
For example imagine this query that gets data on a ``post`` and its ``comments`` and ``reviews``.
14+
15+
{{< highlight graphql "linenos=table" >}}
16+
query {
17+
post {
18+
postText
19+
comments {
20+
commentText
21+
}
22+
reviews {
23+
reviewText {
24+
}
25+
}
26+
27+
{{< / highlight >}}
28+
29+
30+
In this form, you *must* wait for the ``comments`` and ``reviews`` data to be retrieved before you can send the ``post`` data back
31+
to the client. All three data elements are bound to the one query
32+
33+
A naive approach would be to make two queries to get the most important data first but there is now a better way.
34+
35+
There is ``experimental`` support for deferred execution in graphql-java.
36+
37+
{{< highlight graphql "linenos=table" >}}
38+
query {
39+
post {
40+
postText
41+
comments @defer {
42+
commentText
43+
}
44+
reviews @defer {
45+
reviewText {
46+
}
47+
}
48+
49+
{{< / highlight >}}
50+
51+
52+
The ``@defer`` directive tells the engine to defer execution of those fields and deliver them later. The rest of the query is executed as
53+
usual. There will be the usual ``ExecutionResult`` of initial data and then a ``org.reactivestreams.Publisher`` of the deferred data.
54+
55+
In the query above, the ``post`` data will be send out in the initial result and then the comments and review data will be sent (in query order)
56+
down a ``Publisher`` later.
57+
58+
You execute your query as you would any other graphql query. The deferred results ``Publisher`` will be given to you via
59+
the ``extensions`` map
60+
61+
{{< highlight java "linenos=table" >}}
62+
GraphQLSchema schema = buildSchemaWithDirective();
63+
GraphQL graphQL = GraphQL.newGraphQL(schema).build();
64+
65+
//
66+
// deferredQuery contains the query with @defer directives in it
67+
//
68+
ExecutionResult initialResult = graphQL.execute(ExecutionInput.newExecutionInput().query(deferredQuery).build());
69+
70+
//
71+
// then initial results happen first, the deferred ones will begin AFTER these initial
72+
// results have completed
73+
//
74+
sendResult(httpServletResponse, initialResult);
75+
76+
Map<Object, Object> extensions = initialResult.getExtensions();
77+
Publisher<ExecutionResult> deferredResults = (Publisher<ExecutionResult>) extensions.get(GraphQL.DEFERRED_RESULTS);
78+
79+
//
80+
// you subscribe to the deferred results like any other reactive stream
81+
//
82+
deferredResults.subscribe(new Subscriber<ExecutionResult>() {
83+
84+
Subscription subscription;
85+
86+
@Override
87+
public void onSubscribe(Subscription s) {
88+
subscription = s;
89+
//
90+
// how many you request is up to you
91+
subscription.request(10);
92+
}
93+
94+
@Override
95+
public void onNext(ExecutionResult executionResult) {
96+
//
97+
// as each deferred result arrives, send it to where it needs to go
98+
//
99+
sendResult(httpServletResponse, executionResult);
100+
subscription.request(10);
101+
}
102+
103+
@Override
104+
public void onError(Throwable t) {
105+
handleError(httpServletResponse, t);
106+
}
107+
108+
@Override
109+
public void onComplete() {
110+
completeResponse(httpServletResponse);
111+
}
112+
});
113+
114+
{{< / highlight >}}
115+
116+
The above code subscribes to the deferred results and when each one arrives, sends it down to the client.
117+
118+
You can see more details on reactive-streams code here http://www.reactive-streams.org/
119+
120+
``RxJava`` is a popular implementation of reactive-streams. Check out http://reactivex.io/intro.html to find out more
121+
about creating Subscriptions.
122+
123+
graphql-java only produces a stream of deferred results. It does not concern itself with sending these over the network on things
124+
like web sockets and so on. That is important but not a concern of the base graphql-java library. Its up to you
125+
to use whatever network mechanism (websockets / long poll / ....) to get results back to you clients.
126+
127+
Also note that this capability is currently ``experimental`` and not defined by the official ``graphql`` specification. We reserve the
128+
right to change it in a future release or if it enters the official specification. The graphql-java project
129+
is keen to get feedback on this capability.
130+
131+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
title: "Exceptions"
3+
date: 2018-09-09T12:52:46+10:00
4+
draft: false
5+
tags: [documentation]
6+
weight: 109
7+
description: Exceptions
8+
---
9+
# Runtime Exceptions
10+
11+
12+
Runtime exceptions can be thrown by the graphql engine if certain exceptional situations are encountered. The following
13+
are a list of the exceptions that can be thrown all the way out of a ``graphql.execute(...)`` call.
14+
15+
These are not graphql errors in execution but rather totally unacceptable conditions in which to execute a graphql query.
16+
17+
- `graphql.schema.CoercingSerializeException`
18+
19+
is thrown when a value cannot be serialised by a Scalar type, for example
20+
a String value being coerced as an Int.
21+
22+
23+
- `graphql.schema.CoercingParseValueException`
24+
25+
is thrown when a value cannot be parsed by a Scalar type, for example
26+
a String input value being parsed as an Int.
27+
28+
29+
- `graphql.execution.UnresolvedTypeException`
30+
31+
is thrown if a graphql.schema.TypeResolver` fails to provide a concrete
32+
object type given a interface or union type.
33+
34+
35+
- `graphql.execution.NonNullableValueCoercedAsNullException`
36+
37+
is thrown if a non null variable argument is coerced as a
38+
null value during execution.
39+
40+
41+
- `graphql.execution.InputMapDefinesTooManyFieldsException`
42+
43+
is thrown if a map used for an input type object contains
44+
more keys than is defined in that input type.
45+
46+
47+
- `graphql.schema.validation.InvalidSchemaException`
48+
49+
is thrown if the schema is not valid when built via
50+
graphql.schema.GraphQLSchema.Builder#build()`
51+
52+
- `graphql.execution.UnknownOperationException`
53+
54+
if multiple operations are defined in the query and
55+
the operation name is missing or there is no matching operation name
56+
contained in the GraphQL query.
57+
58+
- `graphql.GraphQLException`
59+
60+
is thrown as a general purpose runtime exception, for example if the code cant
61+
access a named field when examining a POJO, it is analogous to a RuntimeException if you will.
62+
63+
64+
- `graphql.AssertException`
65+
66+
is thrown as a low level code assertion exception for truly unexpected code conditions, things we assert
67+
should never happen in practice.
68+

0 commit comments

Comments
 (0)