Skip to content

Instantly share code, notes, and snippets.

@bbakerman
Created April 16, 2019 04:42
Show Gist options
  • Save bbakerman/c445c9c14caefe6f5bdd19050aa38b6a to your computer and use it in GitHub Desktop.
Save bbakerman/c445c9c14caefe6f5bdd19050aa38b6a to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="" />
<link rel="shortcut icon" type="image/x-icon" href="http://localhost:1313//images/favicon.ico">
<title>
GraphQL Java
</title>
<meta name="generator" content="Hugo 0.52" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="http://localhost:1313//css/search.css" />
<link rel="stylesheet" type="text/css" href="http://localhost:1313//css/main.css" />
<link rel="stylesheet" type="text/css" href="http://localhost:1313//css/documentation.css" />
<link rel="stylesheet" type="text/css" href="http://localhost:1313//css/syntax.css" />
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400,200bold,400old" />
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div id="wrap">
<nav class="navbar navbar-default">
<a class="navbar-brand" href="http://localhost:1313/"></a>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<div class="navbar-collapse collapse" id="navbar">
<ul class="nav navbar-nav">
<li><a href="/blog/">BLOG</a></li>
<li><a href="/tutorials/">TUTORIALS</a></li>
<li><a href="/documentation/latest/">DOCUMENTATION</a></li>
<li><a href="/contact/">CONTACT</a></li>
<li><a href="/about/">ABOUT</a></li>
</ul>
<div class="form-inline aa-input-container" id="aa-input-container">
<input type="search" id="aa-search-input" class="aa-input-search" placeholder="Search docs and blogs" name="search"
autocomplete="on" />
<svg class="aa-input-icon" viewBox="654 -372 1664 1664">
<path d="M1806,332c0-123.3-43.8-228.8-131.5-316.5C1586.8-72.2,1481.3-116,1358-116s-228.8,43.8-316.5,131.5 C953.8,103.2,910,208.7,910,332s43.8,228.8,131.5,316.5C1129.2,736.2,1234.7,780,1358,780s228.8-43.8,316.5-131.5 C1762.2,560.8,1806,455.3,1806,332z M2318,1164c0,34.7-12.7,64.7-38,90s-55.3,38-90,38c-36,0-66-12.7-90-38l-343-342 c-119.3,82.7-252.3,124-399,124c-95.3,0-186.5-18.5-273.5-55.5s-162-87-225-150s-113-138-150-225S654,427.3,654,332 s18.5-186.5,55.5-273.5s87-162,150-225s138-113,225-150S1262.7-372,1358-372s186.5,18.5,273.5,55.5s162,87,225,150s113,138,150,225 S2062,236.7,2062,332c0,146.7-41.3,279.7-124,399l343,343C2305.7,1098.7,2318,1128.7,2318,1164z" />
</svg>
</div>
</div>
</nav>
<div class="container">
<h1 class="title">
graphql-java - In Depth - Part 1: DataFetcherResult
</h1>
<div class="panel panel-default">
<div class="panel-body">
<div class="blogpost">
<h1 id="graphql-java-in-depth-series">graphql-java - In Depth series</h1>
<p>Welcome to the new series &ldquo;graphql-java - In Depth&rdquo; where we will explore more specific graphql-java topics.</p>
<h1 id="datafetcherresult">DataFetcherResult</h1>
<p>Today we are looking into the <code>graphql.execution.DataFetcherResult</code> object.</p>
<h1 id="the-scenario">The scenario</h1>
<p>But first lets set the scene. Imagine we have a system that can return <code>issues</code> and the comments on those issues</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-Scala" data-lang="Scala"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-Scala" data-lang="Scala"><span class="o">{</span>
<span class="n">issues</span> <span class="o">{</span>
<span class="n">key</span>
<span class="n">summary</span>
<span class="n">comments</span> <span class="o">{</span>
<span class="n">text</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></td></tr></table>
</div>
</div>
<p><p/></p>
<p>Nominally we would have a <code>graphql.schema.DataFetcher</code> on <code>issues</code> that returns a list of issues and one on the field <code>comments</code> that returns the list of comments
for each issue <code>source</code> object.</p>
<p>As you can see this naively creates an N+1 problem where we need to fetch data multiple times, one for each <code>issue</code> object in isolation.</p>
<p>We could attack this using the <code>org.dataloader.DataLoader</code> pattern but there is another way which will discuss in this article.</p>
<h1 id="look-ahead">Look ahead</h1>
<p>The data fetcher behind the <code>issues</code> field is able to look ahead and see what sub fields are being asked for. In this case it knows that <code>comments</code> are being asked
for and hence it could prefetch them at the same time.</p>
<p><code>graphql.schema.DataFetchingEnvironment#getSelectionSet</code> can be used by data fetcher code to get the selection set of fields for a given parent field.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-Java" data-lang="Java"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-Java" data-lang="Java"> <span class="n">DataFetcher</span> <span class="n">issueDataFetcher</span> <span class="o">=</span> <span class="n">environment</span> <span class="o">-&gt;</span> <span class="o">{</span>
<span class="n">DataFetchingFieldSelectionSet</span> <span class="n">selectionSet</span> <span class="o">=</span> <span class="n">environment</span><span class="o">.</span><span class="na">getSelectionSet</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">selectionSet</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="s">&#34;comments&#34;</span><span class="o">))</span> <span class="o">{</span>
<span class="n">List</span><span class="o">&lt;</span><span class="n">IssueAndCommentsDTO</span><span class="o">&gt;</span> <span class="n">data</span> <span class="o">=</span> <span class="n">getAllIssuesWithComments</span><span class="o">(</span><span class="n">environment</span><span class="o">,</span> <span class="n">selectionSet</span><span class="o">.</span><span class="na">getFields</span><span class="o">());</span>
<span class="k">return</span> <span class="n">data</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">List</span><span class="o">&lt;</span><span class="n">IssueDTO</span><span class="o">&gt;</span> <span class="n">issues</span> <span class="o">=</span> <span class="n">getAllIssuesWitNoComments</span><span class="o">(</span><span class="n">environment</span><span class="o">);</span>
<span class="k">return</span> <span class="n">issues</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">};</span></code></pre></td></tr></table>
</div>
</div>
<p>Imagine this is backed by an SQL system we might be able to use this field look ahead to produce the following SQL</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-Sql" data-lang="Sql"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-Sql" data-lang="Sql"> <span class="k">SELECT</span> <span class="n">Issues</span><span class="p">.</span><span class="k">Key</span><span class="p">,</span> <span class="n">Issues</span><span class="p">.</span><span class="n">Summary</span><span class="p">,</span> <span class="n">Comments</span><span class="p">.</span><span class="nb">Text</span>
<span class="k">FROM</span> <span class="n">Issues</span>
<span class="k">INNER</span> <span class="k">JOIN</span> <span class="n">Comments</span> <span class="k">ON</span> <span class="n">Issues</span><span class="p">.</span><span class="n">CommentID</span><span class="o">=</span><span class="n">Comments</span><span class="p">.</span><span class="n">ID</span><span class="p">;</span></code></pre></td></tr></table>
</div>
</div>
<p>So we have looked ahead and returned different data depending on the field sub selection. We have made out system more efficient by using look ahead
and fetched data just the 1 time and not N+1 times.</p>
<h1 id="challenges">Challenges</h1>
<p>The challenge with this is that shape of the returns data is now field sub selection specific. We needed a <code>IssueAndCommentsDTO</code> for one sub selection
path and a simpler <code>IssueDTO</code> for another path.</p>
<p>With enough paths this becomes problematic as it adds new DTO classes per path.</p>
<p>Also the standard graphql pattern is that the returned object becomes the <code>source</code> aka <code>graphql.schema.DataFetchingEnvironment#getSource</code> of the next child
data fetcher. But we might have pre fetched data that is need 2 levels deep and this is challenging to do since each data fetcher would need to capture and copy
that down to the layers below.</p>
<h1 id="passing-data-and-local-context">Passing Data and Local Context</h1>
<p>graphql-java offers a capability that helps with this pattern. We go beyond what the reference graphql-js system gives you where the object is you
returned is automatically the <code>source</code> of the next child fetcher.</p>
<p>You can use <code>graphql.execution.DataFetcherResult</code> to return three sets of values</p>
<ul>
<li><code>data</code> - which will be used as the source of the next set of fields</li>
<li><code>errors</code> - allowing you to return data as well as errors</li>
<li><code>localContext</code> - which allows you to pass down field specific context</li>
</ul>
<p>In our example case we will be use <code>data</code> and <code>localContext</code> to communicate between fields easily.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-Java" data-lang="Java"><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-Java" data-lang="Java"><span class="n">DataFetcher</span> <span class="n">issueDataFetcher</span> <span class="o">=</span> <span class="n">environment</span> <span class="o">-&gt;</span> <span class="o">{</span>
<span class="n">DataFetchingFieldSelectionSet</span> <span class="n">selectionSet</span> <span class="o">=</span> <span class="n">environment</span><span class="o">.</span><span class="na">getSelectionSet</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">selectionSet</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="s">&#34;comments&#34;</span><span class="o">))</span> <span class="o">{</span>
<span class="n">List</span><span class="o">&lt;</span><span class="n">IssueAndCommentsDTO</span><span class="o">&gt;</span> <span class="n">data</span> <span class="o">=</span> <span class="n">getAllIssuesWithComments</span><span class="o">(</span><span class="n">environment</span><span class="o">,</span> <span class="n">selectionSet</span><span class="o">.</span><span class="na">getFields</span><span class="o">());</span>
<span class="n">List</span><span class="o">&lt;</span><span class="n">IssueDTO</span><span class="o">&gt;</span> <span class="n">issues</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="n">dto</span> <span class="o">-&gt;</span> <span class="n">dto</span><span class="o">.</span><span class="na">getIssue</span><span class="o">()).</span><span class="na">collect</span><span class="o">(</span><span class="n">toList</span><span class="o">());</span>
<span class="n">Map</span><span class="o">&lt;</span><span class="n">IssueDTO</span><span class="o">,</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">CommentDTO</span><span class="o">&gt;&gt;</span> <span class="n">preFetchedComments</span> <span class="o">=</span> <span class="n">mkMapOfComments</span><span class="o">(</span><span class="n">data</span><span class="o">);</span>
<span class="k">return</span> <span class="n">DataFetcherResult</span><span class="o">.</span><span class="na">newResult</span><span class="o">()</span>
<span class="o">.</span><span class="na">data</span><span class="o">(</span><span class="n">issues</span><span class="o">)</span>
<span class="o">.</span><span class="na">localContext</span><span class="o">(</span><span class="n">preFetchedComments</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">List</span><span class="o">&lt;</span><span class="n">IssueDTO</span><span class="o">&gt;</span> <span class="n">issues</span> <span class="o">=</span> <span class="n">getAllIssuesWitNoComments</span><span class="o">(</span><span class="n">environment</span><span class="o">);</span>
<span class="k">return</span> <span class="n">DataFetcherResult</span><span class="o">.</span><span class="na">newResult</span><span class="o">()</span>
<span class="o">.</span><span class="na">data</span><span class="o">(</span><span class="n">issues</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">};</span></code></pre></td></tr></table>
</div>
</div>
<p>If you look now you will see that our data fetcher returns a compound object that contains data for the child data fetchers which is the list of <code>issueDTO</code> objects
as per usual.</p>
<p>It also passes down field specific <code>localContext</code> which is the pre-fetched comment data map keyed by <code>issueDTO</code> object.</p>
<p>Unlike the global context object, local context objects are passed down from a specific field to its children and are not shared across to peer fields. This means
a parent field has a &ldquo;back channel&rdquo; to talk to the child fields without having to &ldquo;pollute&rdquo; the DTO source objects with that information and it is local in the sense
that it this field and its children and not any other field in the query.</p>
<p>Now lets look at the <code>comments</code> data fetcher and how it consumes this data</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-Java" data-lang="Java"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-Java" data-lang="Java"> <span class="n">DataFetcher</span> <span class="n">commentsDataFetcher</span> <span class="o">=</span> <span class="n">environment</span> <span class="o">-&gt;</span> <span class="o">{</span>
<span class="n">IssueDTO</span> <span class="n">issueDTO</span> <span class="o">=</span> <span class="n">environment</span><span class="o">.</span><span class="na">getSource</span><span class="o">();</span>
<span class="n">Map</span><span class="o">&lt;</span><span class="n">IssueDTO</span><span class="o">,</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">CommentDTO</span><span class="o">&gt;&gt;</span> <span class="n">preFetchedComments</span> <span class="o">=</span> <span class="n">environment</span><span class="o">.</span><span class="na">getLocalContext</span><span class="o">();</span>
<span class="n">List</span><span class="o">&lt;</span><span class="n">CommentDTO</span><span class="o">&gt;</span> <span class="n">commentDTOS</span> <span class="o">=</span> <span class="n">preFetchedComments</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">issueDTO</span><span class="o">);</span>
<span class="k">return</span> <span class="n">DataFetcherResult</span><span class="o">.</span><span class="na">newResult</span><span class="o">()</span>
<span class="o">.</span><span class="na">data</span><span class="o">(</span><span class="n">commentDTOS</span><span class="o">)</span>
<span class="o">.</span><span class="na">localContext</span><span class="o">(</span><span class="n">preFetchedComments</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">};</span></code></pre></td></tr></table>
</div>
</div>
<p>Notice how it got the <code>issueDTO</code> as its source object as expected but it also got a local context object which is our pre-fetched comments. It can choose
to pass on new local context OR if it passes nothing then the previous value will bubble down to the next lot of child fields. So you can think of <code>localContext</code>
as being inherited unless a fields data fetcher explicitly overrides it.</p>
<h1 id="passing-back-errors-or-data-or-both">Passing back Errors or Data or both</h1>
<p>For completeness we will show you that you can also pass down errors or data or local context or all of the them.</p>
<p>It is perfectly valid to fetch data in graphql and to ALSO send back errors. Its not common but its valid. For example you might be able to select issues data but
not the associated comment data. Some data is better than no data.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code class="language-Java" data-lang="Java"><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-Java" data-lang="Java"> <span class="n">GraphQLError</span> <span class="n">error</span> <span class="o">=</span> <span class="n">mkSpecialError</span><span class="o">(</span><span class="s">&#34;Its Tuesday&#34;</span><span class="o">);</span>
<span class="k">return</span> <span class="n">DataFetcherResult</span><span class="o">.</span><span class="na">newResult</span><span class="o">()</span>
<span class="o">.</span><span class="na">data</span><span class="o">(</span><span class="n">commentDTOS</span><span class="o">)</span>
<span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="n">error</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span></code></pre></td></tr></table>
</div>
</div>
</div>
</div>
<hr>
</div>
</div>
</div>
<footer>
<div id="footer">
<div class="container">
<p class="text-muted">&copy; Andreas Marek. Powered by <a href="https://gohugo.io/">Hugo</a> and
<a href="http://www.github.com/nurlansu/hugo-sustain/">sustain</a> with ♥</p>
</div>
</div>
</footer>
<div class="footer"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="http://localhost:1313//js/docs.min.js"></script>
<script src="http://localhost:1313//js/main.js"></script>
<script src="http://localhost:1313//js/ie10-viewport-bug-workaround.js"></script>
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-126627606-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-126627606-1');
</script>
<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.min.js"></script>
<script src="http://localhost:1313//js/search.js"></script>
<script data-no-instant>document.write('<script src="/livereload.js?port=1313&mindelay=10"></' + 'script>')</script></body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment