Getting Start With GraphQL for Java
Preface
By the evolving of the API design, more and more developer are focus on the RESTFUL API style. But also more and more questions comes. How to do the versioning? How to design the response body to meet different requirement? especially when client wants different fields by one request. How to validating the parameter type and logic? How to fetching and caching the data? So here comes the GrapqhQL, it’s let us have another choice to design the API beside the RESTFUL.
What is GraphQL?
Here is the introduction from GraphQL official web site.
GraphQL is a query language designed to build client applications by providing an intuitive and flexible syntax
and system for describing their data requirements and interactions.
GraphQL is not a programming language capable of arbitrary computation, but is instead a language used to query
application servers that have capabilities defined in this specification. GraphQL does not mandate a particular
programming language or storage system for application servers that implement it. Instead, application servers take
their capabilities and map them to a uniform language, type system, and philosophy that GraphQL encodes.
This provides a unified interface friendly to product development and a powerful platform for tool‐building.
Compare to RESTFUL, GraphQL have 5 advantages
- Easy to extend
- Query as documentation
- Fetch the data more centralized
- Strong type system
- No more versioning
Let’s see what Github says~
You may be wondering why we chose to start supporting GraphQL. Our API was designed to be RESTful and
hypermedia-driven. We’re fortunate to have dozens of different open-source clients written in a plethora of
languages. Businesses grew around these endpoints.
Like most technology, REST is not perfect and has some drawbacks. Our ambition to change our API focused on solving
two problems.
The first was scalability. The REST API is responsible for over 60% of the requests made to our database tier. This
is partly because, by its nature, hypermedia navigation requires a client to repeatedly communicate with a server
so that it can get all the information it needs. Our responses were bloated and filled with all sorts of *_url hints
in the JSON responses to help people continue to navigate through the API to get what they needed. Despite all the
information we provided, we heard from integrators that our REST API also wasn’t very flexible. It sometimes required
two or three separate calls to assemble a complete view of a resource. It seemed like our responses simultaneously
sent too much data and didn’t include data that consumers needed.
As we began to audit our endpoints in preparation for an APIv4, we encountered our second problem. We wanted to
collect some meta-information about our endpoints. For example, we wanted to identify the OAuth scopes required for
each endpoint. We wanted to be smarter about how our resources were paginated. We wanted assurances of type-safety
for user-supplied parameters. We wanted to generate documentation from our code. We wanted to generate clients
instead of manually supplying patches to our Octokit suite. We studied a variety of API specifications built to
make some of this easier, but we found that none of the standards totally matched our requirements.
And then we learned about GraphQL.
Getting start
So let’s get familiar with it from a simple example
type Query {
hello: String
}
This is a schema
definition for the query. We define a root type named Query
, and from this type we can get hello
field, which is in the type of String
Then assume that we build a GraphQL server and make it running already. Now we can send a query to retrieve the value for hello
.
query Query {
hello
}
Let’s see what we get.
{hello=world}
So here we should know some main concepts of GraphQL
Operation
GraphQL have two operations, query
& mutation
. For example, the schema design could be:
type Mutation {
setMessage(message: String): String
}
type Query {
getMessage: String
}
Schema
Schema is the query definitions of the GraphQL, it’s contain the subtypes, fields, interfaces, input objects, enum defines… for example
type Query {
patent_biblio: PatentBiblio
legal: Legal
patent_biblio_legal: PatentBiblioAndLegal
}
interface Patent {
id(patentId: String): String!
pn: String!
}
type PatentBiblio implements Patent{
id(patentId: String): String!
pn: String!
apno: String
ans: Assignee
familyType: FamilyType
}
type Assignee implements Person{
name: String!
lang: String!
}
interface Person {
name: String!
}
type Legal {
legalStatus:[String]
eventStatus:[String]
l001ep:String
}
union PatentBiblioAndLegal = PatentBiblio | Legal
enum FamilyType {
ORIGINAL
INPADOC
}
This schema define a Query
type that could let client fetch PatentBiblio
and Legal
. The PatentBiblio
implements the Patent
interface, and also PatentBiblio
contains Assignee
type which implements the Person
type. The familyType
in PatentBiblio will return the enum type named FamilyType
.
The structure is like below:
Field
In the example below, pn
is a field of Patent
, and it’s type is String
.
Argument
In the example below, patentId
is a argument of id
field in PatentBiblio
, and it’s type is String
.
Main Types of the GraphQL
- Scalar
- Int
- Float
- String
- Boolean
- Object
- Interface
- Inpit Object
- Enum
- List
As we know, GraphQL is based on strong type validating, so we must be very clear about what kinds of type we want to define.
For example, in the example below, we can see that:
patent_biblio
is related to Object PatentBiblio
legalStatus
in Legal
is the List
type, the format could be [List]
familyType
in PatentBiblio
related to Enum
type FamilyType
schema {
query: LitigationQuery
}
type LitigationQuery {
litigation: Litigation
}
type Litigation {
defendant(queryInput: LitigationQueryInput): String
plaintiff: String
}
input LitigationQueryInput {
patentId: String
}
In this example, we make the argument in field defendant
to be the Input Object
type namedLitigationQueryInput
.
Fragment
In order to reduce the duplicate text in the query string, GraphQL allow for the reuse of the repeated selections of the fields, for example:
query Query($patentId: String!, $offset: Int, $limit: Int) {
patent(patentId: $patentId, offset: $offset, limit: $limit) {
id
pn
my_name
person {
name
}
citations {
...citationPatent
citations {
...citationPatent
citations {
...citationPatent
}
}
}
}
total(patentId: $patentId)
offset(offset: $offset, limit: $limit)
}
fragment citationPatent on Patent {
id
pn
}
GraphQL for Java
GraphQL library support many languages, such as
- C# / .NET
- Clojure
- Elixir
- Erlang
- Go
- Groovy
- Java
- JavaScript
- PHP
- Python
- Scala
- Ruby
Before we start to introduce the GraphQL for Java, let’s think about some questions first.
- How we define the schema?
- How we fetch the data?
- How to send query?
- How to validating the query?
- How to serialized the response?
- How to catch the exception?
- How to do the logging?
- How to do the pagination?
- How to serve vai HTTP/HTTPS?
So let’s begin from an Hello World again! Create a maven architecture project first, add the dependency for GraphQL
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chris.graphql</groupId>
<artifactId>project-graphql</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>project-graphql Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>6.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.3</version>
</dependency>
</dependencies>
<build>
<finalName>project-graphql</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<optimize>true</optimize>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
Because the GraphQL-Java based on Java 8, so we also make the project compile in JDK 1.8, then let’s create a class named HelloWorld
package com.chris.graphql;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.Scalars;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
/**
* com.chris.graphql.HelloWorld
* <p>
* Author: ChrisYe
* Date: 11/28/2017
*/
public class HelloWorld {
public static void main(String... args) {
// Programmatically
GraphQLObjectType queryType = GraphQLObjectType.newObject()
.name("Query")
.field(GraphQLFieldDefinition.newFieldDefinition()
.name("hello")
.type(Scalars.GraphQLString)
.dataFetcher(env -> "world"))
.build();
//Query by
GraphQLSchema graphQLSchema = GraphQLSchema.newSchema()
.query(queryType)
.build();
GraphQL build = GraphQL.newGraphQL(graphQLSchema).build();
ExecutionResult executionResult = build.execute("query Query {hello}");
System.out.println(executionResult.getData().toString());
}
}
Run the main method, and get the result:
{hello=world}
OK, that’s it. In this example, we define the schema programmatically.
First we define a GraphQLObjectType
, which named Query
and have field hello
, the type of it is String
, also we define a static datfetcher
for this field, let it return the value of world
for field hello
.
Second we generate the schema
and build a executable GraphQL
object vai this schema.
Third we send a query string query Query {hello}
to the GraphQL
to get the result.
We should know that we could define the schema in 3 ways.
- By String
- By IDL file
- By Program
By String
// IDL string schema
String schema = "type Query{hello: String}";
SchemaParser schemaParser = new SchemaParser();
TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema);
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder.dataFetcher("hello", new StaticDataFetcher("world")))
.build();
SchemaGenerator schemaGenerator = new SchemaGenerator();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
GraphQL build = GraphQL.newGraphQL(graphQLSchema).build();
By IDL file
// IDL file shcema
File schema = new File(HelloWorld.class.getClassLoader().getResource("helloworld.graphqls").getFile());
SchemaParser schemaParser = new SchemaParser();
TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema);
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder.dataFetcher("hello", new StaticDataFetcher("world")))
.build();
SchemaGenerator schemaGenerator = new SchemaGenerator();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
GraphQL build = GraphQL.newGraphQL(graphQLSchema).build();
The file in resource folder named helloworld.graphqls
type Query {
hello: String
}
By program
// Programmatically
GraphQLObjectType queryType = GraphQLObjectType.newObject()
.name("Query")
.field(GraphQLFieldDefinition.newFieldDefinition()
.name("hello")
.type(Scalars.GraphQLString)
.dataFetcher(env -> "world"))
.build();
//Query by
GraphQLSchema graphQLSchema = GraphQLSchema.newSchema()
.query(queryType)
.build();
GraphQL build = GraphQL.newGraphQL(graphQLSchema).build();
Let’s get more deep on the GraphQL java, this time, we will make every thing together, and we try to fullfill more points & functions on GraphQL Java
- Schema - Object
- Schema - Interface
- Schema - Enumeration
- Schema - Variable & attrubute
- Schema parse cache
- Data fetcher
- Type resolver
- Execution input
- Transfer result to regular json
So let’s start from another example Step one, define the schema, put it in the resource folder
type Query {
patent_biblio: PatentBiblio
legal: Legal
patent_biblio_legal: PatentBiblioAndLegal
}
interface Patent {
id(patentId: String): String!
pn: String!
}
type PatentBiblio implements Patent{
id(patentId: String): String!
pn: String!
apno: String
ans: Assignee
familyType: FamilyType
}
type Assignee implements Person{
name: String!
lang: String!
}
interface Person {
name: String!
}
type Legal {
legalStatus:[String]
eventStatus:[String]
l001ep:String
}
union PatentBiblioAndLegal = PatentBiblio | Legal
enum FamilyType {
ORIGINAL
INPADOC
}
type QueryLitigation {
litigation: Litigation
}
type Litigation {
defendant(queryInput: LitigationQueryInput): String
plaintiff: String
}
input LitigationQueryInput {
patentId: String
}
Step two, write test class, NestObjTest.class
package com.chris.graphql;
import com.chris.graphql.entity.Assignee;
import com.chris.graphql.entity.FamilyType;
import com.chris.graphql.entity.Legal;
import com.chris.graphql.entity.PatentBiblio;
import com.chris.graphql.entity.PatentBiblioAndLegal;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.io.File;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.execution.preparsed.PreparsedDocumentEntry;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import graphql.schema.idl.TypeRuntimeWiring;
/**
* Created by ye830 on 12/2/2017.
*/
public class NestObjTest {
public static void main(String... args) {
// Define static value
PatentBiblio patentBiblio = new PatentBiblio();
patentBiblio.setId("88d378a5-3909-4e9c-82de-55dd70e4685c");
patentBiblio.setPn("IL168994A");
patentBiblio.setApno("IL168994");
patentBiblio.setFamilyType(FamilyType.INPADOC);
Assignee assignee = new Assignee();
assignee.setName("ChrisYe");
assignee.setLang("EN");
patentBiblio.setAns(assignee);
Legal legal = new Legal();
legal.setLegalStatus(Arrays.asList("1", "2"));
legal.setEventStatus(Arrays.asList("61", "62"));
legal.setL001ep("EP");
PatentBiblioAndLegal patentBiblioAndLegal = new PatentBiblioAndLegal();
patentBiblioAndLegal.setPatentBiblio(patentBiblio);
patentBiblioAndLegal.setLegal(legal);
// Define the schema
File schema = new File(NestObjTest.class.getClassLoader().getResource("nestobj.graphqls").getPath());
// Define the TypeDefineRegistry
TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(schema);
// Define the DataFetcher
DataFetcher patentIdDataFetcher = env -> {
Object patentId = env.getArgument("patentId");
if (patentId != null) {
return patentId;
}
return patentBiblio.getId();
};
// Define the run time wiring, map the value bean and DataFetcher to the type or fields
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder
// patent_biblio -> value bean
// patent_biblio_legal -> value bean
.dataFetcher("patent_biblio", env -> patentBiblio)
.dataFetcher("patent_biblio_legal", env -> legal)
)
// PatentBiblio -> id -> patentIdDataFetcher
.type("PatentBiblio", builder -> builder
.dataFetcher("id", patentIdDataFetcher))
// Define the type resolver for the interface Patent
.type(TypeRuntimeWiring.newTypeWiring("Patent").typeResolver(env -> {
Object javaObject = env.getObject();
if (javaObject instanceof PatentBiblio) {
return (GraphQLObjectType) env.getSchema().getType("PatentBiblio");
} else {
return (GraphQLObjectType) env.getSchema().getType("PatentBiblio");
}
}))
// Define the typeResolver for the interface Person
.type(TypeRuntimeWiring.newTypeWiring("Person").typeResolver(env -> {
Object javaObject = env.getObject();
if (javaObject instanceof Assignee) {
return (GraphQLObjectType) env.getSchema().getType("Assignee");
} else {
return (GraphQLObjectType) env.getSchema().getType("Assignee");
}
}))
// Define the typeResolver for the union type PatentBiblioAndLegal
.type(TypeRuntimeWiring.newTypeWiring("PatentBiblioAndLegal").typeResolver(env -> {
Object javaObject = env.getObject();
if (javaObject instanceof PatentBiblio) {
return (GraphQLObjectType) env.getSchema().getType("PatentBiblio");
}
if (javaObject instanceof Legal) {
return (GraphQLObjectType) env.getSchema().getType("Legal");
} else {
return (GraphQLObjectType) env.getSchema().getType("PatentBiblio");
}
}))
.build();
// Define the GraphQLSchema
GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
// Define the cache, using for documentation parsing
Cache<String, PreparsedDocumentEntry> cache = Caffeine.newBuilder().maximumSize(10_000).build();
// Define the GraphQL
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema)
.preparsedDocumentProvider(cache::get)
.build();
// Define the query string
String queryString = "query Query($patentId: String!){patent_biblio {id(patentId: $patentId) pn apno ans{name lang} familyType}}";
// Define the variableMap
Map<String, Object> variableMap = new HashMap<>();
variableMap.put("patentId", "1111111");
// Define the ExecutionInput
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(queryString)
.operationName("Query")
.variables(variableMap)
.build();
Long startTime = new Date().getTime();
// Turn it into regular json
ExecutionResult executionResult = graphQL.execute(executionInput);
Map<String, Object> specificationResult = executionResult.toSpecification();
ObjectMapper objectMapper = new ObjectMapper();
try {
System.out.println(objectMapper.writeValueAsString(specificationResult));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
Long endTime = new Date().getTime();
System.out.println("Cost:" + String.valueOf(endTime - startTime) + " ms");
// Execute the query once more, compare the time cost to check if cache works
startTime = new Date().getTime();
queryString = "query Query($patentId: String!){patent_biblio {id(patentId: $patentId) pn apno ans{name lang} familyType}}";
variableMap = new HashMap<>();
variableMap.put("patentId", "1111111");
executionInput = ExecutionInput.newExecutionInput()
.query(queryString)
.operationName("Query")
.variables(variableMap)
.build();
executionResult = graphQL.execute(executionInput);
specificationResult = executionResult.toSpecification();
objectMapper = new ObjectMapper();
try {
System.out.println(objectMapper.writeValueAsString(specificationResult));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
endTime = new Date().getTime();
System.out.println("Cost:" + String.valueOf(endTime - startTime) + " ms");
}
}
Step 3, define some beans, correspondence with the schema
/**
* Created by ye830 on 12/2/2017.
*/
public class Assignee extends Person {
private String lang;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
}
package com.chris.graphql.entity;
/**
* Created by ye830 on 12/8/2017.
*/
public enum FamilyType {
ORIGINAL("ORIGINAL"),
INPADOC("INPADOC");
private String familyType;
private FamilyType(String familyType){
this.familyType = familyType;
}
public String getFamilyType() {
return familyType;
}
public void setFamilyType(String familyType) {
this.familyType = familyType;
}
}
package com.chris.graphql.entity;
import java.util.List;
/**
* Created by ye830 on 12/8/2017.
*/
public class Legal {
private String l001ep;
private List<String> legalStatus;
private List<String> eventStatus;
public String getL001ep() {
return l001ep;
}
public void setL001ep(String l001ep) {
this.l001ep = l001ep;
}
public List<String> getLegalStatus() {
return legalStatus;
}
public void setLegalStatus(List<String> legalStatus) {
this.legalStatus = legalStatus;
}
public List<String> getEventStatus() {
return eventStatus;
}
public void setEventStatus(List<String> eventStatus) {
this.eventStatus = eventStatus;
}
}
package com.chris.graphql.entity;
/**
* Created by ye830 on 12/2/2017.
*/
public class PatentBiblio extends Patent {
private String apno;
private Person ans;
private FamilyType familyType;
public String getApno() {
return apno;
}
public void setApno(String apno) {
this.apno = apno;
}
public Person getAns() {
return ans;
}
public void setAns(Person ans) {
this.ans = ans;
}
public FamilyType getFamilyType() {
return familyType;
}
public void setFamilyType(FamilyType familyType) {
this.familyType = familyType;
}
}
package com.chris.graphql.entity;
/**
* Created by ye830 on 12/2/2017.
*/
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.chris.graphql.entity;
/**
* Created by ye830 on 12/2/2017.
*/
public class PatentBiblioAndLegal {
private PatentBiblio patentBiblio;
private Legal legal;
public PatentBiblio getPatentBiblio() {
return patentBiblio;
}
public void setPatentBiblio(PatentBiblio patentBiblio) {
this.patentBiblio = patentBiblio;
}
public Legal getLegal() {
return legal;
}
public void setLegal(Legal legal) {
this.legal = legal;
}
}
Then let’s run the code
{"data":{"patent_biblio":{"id":"1111111","pn":"IL168994A","apno":"IL168994","ans":{"name":"ChrisYe","lang":"EN"},"familyType":"INPADOC"}}}
Cost:1699 ms
{"data":{"patent_biblio":{"id":"1111111","pn":"IL168994A","apno":"IL168994","ans":{"name":"ChrisYe","lang":"EN"},"familyType":"INPADOC"}}}
Cost:3 ms
The whole pictures of this example is like below:
From this example, we could see that:
- DataFetcher is a function interface, we could use the environment to get the variables
- GraphQL Java will automatically map the bean fields with the data result
- TypeResolver is needed when some type is implement the interface
- The cache works fine for the documentation parsing, suggest to use Caffeine Cache
- Use
toSpecification
to turn the result into Map object, so we can turn it into regular json object by using serialize tools
So let’s write more examples for Exception
, Validation
, Batch Get
, Asynchronization fetch
,
Exception
package com.chris.graphql;
import com.chris.graphql.entity.Litigation;
import java.io.File;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import graphql.ErrorType;
import graphql.ExceptionWhileDataFetching;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.GraphQLError;
import graphql.execution.AsyncExecutionStrategy;
import graphql.language.SourceLocation;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import graphql.schema.idl.TypeRuntimeWiring;
/**
* Created by ye830 on 12/2/2017.
*/
public class ExceptionTest {
public static void main(String... args) {
Litigation litigation = new Litigation();
litigation.setDefendant("Zhang San");
litigation.setPlaintiff("Li Si");
// Define the schema
File schema = new File(ExceptionTest.class.getClassLoader().getResource("inputobj.graphqls").getPath());
// Define the TypeDefineRegistry
TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(schema);
// Define the run time wiring
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("LitigationQuery", builder -> builder
.dataFetcher("litigation", env -> {
throw new CustomRuntimeException();
})
)
.type("Litigation", builder -> builder
.dataFetcher("defendant", env -> {
Object queryInput = env.getArgument("queryInput");
if(queryInput != null){
Object patentId = Optional.ofNullable(((HashMap)queryInput).get("patentId")).orElse("6666666666666");
return patentId;
}
return "6666666666666";
}))
.type(TypeRuntimeWiring.newTypeWiring("Litigation").typeResolver(env -> {
Object javaObject = env.getObject();
if (javaObject instanceof Litigation) {
return (GraphQLObjectType) env.getSchema().getType("Litigation");
} else {
return (GraphQLObjectType) env.getSchema().getType("Litigation");
}
}))
.build();
// Define the GraphQLSchema
GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
// Define the GraphQL
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema)
// .queryExecutionStrategy(new AsyncExecutionStrategy(handlerParameters -> {
// ExceptionWhileDataFetching error = new ExceptionWhileDataFetching(handlerParameters.getPath(), handlerParameters.getException(), handlerParameters.getField().getSourceLocation());
// handlerParameters.getExecutionContext().addError(error, handlerParameters.getPath());
// }))
.build();
String queryString = "query LitigationQuery($input: LitigationQueryInput){litigation{defendant(queryInput: $input)}}";
Map<String,Object> variableMap = new HashMap<>();
Map<String,Object> litigationQueryInput = new HashMap<>();
litigationQueryInput.put("patentId", "2222222222222");
variableMap.put("input", litigationQueryInput);
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(queryString)
.operationName("LitigationQuery")
.variables(variableMap)
.build();
ExecutionResult executionResult = graphQL.execute(executionInput);
executionResult.getErrors().stream().forEach(error -> {
System.out.println("my customized exception:");
System.out.println(error.getExtensions().get("foo"));
System.out.println(error.getExtensions().get("fizz"));
System.out.println(error.getExtensions().get("message"));
});
}
static class CustomRuntimeException extends RuntimeException implements GraphQLError {
@Override
public Map<String, Object> getExtensions() {
Map<String, Object> customAttributes = new LinkedHashMap<>();
customAttributes.put("foo", "bar");
customAttributes.put("fizz", "whizz");
return customAttributes;
}
@Override
public List<SourceLocation> getLocations (){
return null;
}
@Override
public ErrorType getErrorType() {
return ErrorType.DataFetchingException;
}
}
}
If an exception happens during the data fetcher call, GraphQL will throws the graphql.ExceptionWhileDataFetching
error, and if the exception you throw is the GraphQLError
, then GraphQL will transfer it into the graphql.ExceptionWhileDataFetching
So this means you can customize your own exception.
In the example above, we define a customized exception named CustomRuntimeException
, implements the GraphQLError, and add two extention attributes, then run the code, the result will be
19:47:07.933 [main] WARN g.e.SimpleDataFetcherExceptionHandler - Exception while fetching data (/litigation) : null
com.chris.graphql.ExceptionTest$CustomRuntimeException: null
at com.chris.graphql.ExceptionTest.lambda$null$0(ExceptionTest.java:47)
at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:219)
at graphql.execution.ExecutionStrategy.resolveField(ExecutionStrategy.java:165)
at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:55)
at graphql.execution.Execution.executeOperation(Execution.java:154)
at graphql.execution.Execution.execute(Execution.java:98)
at graphql.GraphQL.execute(GraphQL.java:546)
at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:488)
at graphql.GraphQL.executeAsync(GraphQL.java:463)
at graphql.GraphQL.execute(GraphQL.java:394)
at com.chris.graphql.ExceptionTest.main(ExceptionTest.java:91)
my customized exception:
bar
whizz
null
Imagine that you don’t like the exception message and want to define it by your self, so you define the AsyncExecutionStrategy
to change the exception behavior
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema)
.queryExecutionStrategy(new AsyncExecutionStrategy(handlerParameters -> {
ExceptionWhileDataFetching error = new ExceptionWhileDataFetching(handlerParameters.getPath(), handlerParameters.getException(), handlerParameters.getField().getSourceLocation());
handlerParameters.getExecutionContext().addError(error, handlerParameters.getPath());
}))
.build();
Asynchronized data fetching
package com.chris.graphql;
import com.chris.graphql.entity.Assignee;
import com.chris.graphql.entity.FamilyType;
import com.chris.graphql.entity.Legal;
import com.chris.graphql.entity.PatentBiblio;
import com.chris.graphql.entity.PatentBiblioAndLegal;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import graphql.schema.idl.TypeRuntimeWiring;
/**
* Created by ye830 on 12/2/2017.
*/
public class AsynTest {
public static void main(String... args) {
Long startTime = new Date().getTime();
// Define static value
PatentBiblio patentBiblio = new PatentBiblio();
patentBiblio.setId("88d378a5-3909-4e9c-82de-55dd70e4685c");
patentBiblio.setPn("IL168994A");
patentBiblio.setApno("IL168994");
patentBiblio.setFamilyType(FamilyType.INPADOC);
Assignee assignee = new Assignee();
assignee.setName("ChrisYe");
assignee.setLang("EN");
patentBiblio.setAns(assignee);
Legal legal = new Legal();
legal.setLegalStatus(Arrays.asList("1", "2"));
legal.setEventStatus(Arrays.asList("61", "62"));
legal.setL001ep("EP");
PatentBiblioAndLegal patentBiblioAndLegal = new PatentBiblioAndLegal();
patentBiblioAndLegal.setPatentBiblio(patentBiblio);
patentBiblioAndLegal.setLegal(legal);
// Define the schema
File schema = new File(AsynTest.class.getClassLoader().getResource("nestobj.graphqls").getPath());
// Define the TypeDefineRegistry
TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(schema);
// Define the run time wiring
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder
.dataFetcher("patent_biblio", env -> {
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
try {
System.out.println("i am in patent_biblio");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return patentBiblio;
});
return future;
})
.dataFetcher("legal", env -> {
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
try {
System.out.println("i am in patent_biblio_legal");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return legal;
});
return future;
})
)
// .type("Query", builder -> builder
// .dataFetcher("patent_biblio", env -> {
// try {
// System.out.println("i am in patent_biblio");
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// return patentBiblio;
// })
// .dataFetcher("legal", env -> {
// try {
// System.out.println("i am in legal");
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// return legal;
// })
// )
.type("PatentBiblio", builder -> builder
.dataFetcher("id", env -> {
Object patentId = env.getArgument("patentId");
if (patentId != null) {
return patentId;
}
return patentBiblio.getId();
}))
.type(TypeRuntimeWiring.newTypeWiring("Patent").typeResolver(env -> {
Object javaObject = env.getObject();
if (javaObject instanceof PatentBiblio) {
return (GraphQLObjectType) env.getSchema().getType("PatentBiblio");
} else {
return (GraphQLObjectType) env.getSchema().getType("PatentBiblio");
}
}))
.type(TypeRuntimeWiring.newTypeWiring("Person").typeResolver(env -> {
Object javaObject = env.getObject();
if (javaObject instanceof Assignee) {
return (GraphQLObjectType) env.getSchema().getType("Assignee");
} else {
return (GraphQLObjectType) env.getSchema().getType("Assignee");
}
}))
.type(TypeRuntimeWiring.newTypeWiring("PatentBiblioAndLegal").typeResolver(env -> {
Object javaObject = env.getObject();
if (javaObject instanceof PatentBiblio) {
return (GraphQLObjectType) env.getSchema().getType("PatentBiblio");
}
if (javaObject instanceof Legal) {
return (GraphQLObjectType) env.getSchema().getType("Legal");
} else {
return (GraphQLObjectType) env.getSchema().getType("PatentBiblio");
}
}))
.build();
// Define the GraphQLSchema
GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
// Define the GraphQL
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
// Execute the query
// String queryString = "{patent_biblio {id pn apno ans{name lang} familyType}}";
// Execute the union query
// String queryString = "{patent_biblio_legal {... on PatentBiblio {id} ... on Legal {legalStatus}}}";
//ExecutionResult executionResult = graphQL.execute(queryString);
// Query by ExecutionInput
String queryString = "query Query($patentId: String!){patent_biblio {id(patentId: $patentId) pn apno ans{name lang} familyType} legal {legalStatus}}";
Map<String, Object> variableMap = new HashMap<>();
variableMap.put("patentId", "1111111");
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(queryString)
.operationName("Query")
.variables(variableMap)
.build();
CompletableFuture<ExecutionResult> completableFuture = graphQL.executeAsync(executionInput);
Map<String, Object> specificationResult;
try {
specificationResult = completableFuture.get(10, TimeUnit.SECONDS).toSpecification();
ObjectMapper objectMapper = new ObjectMapper();
try {
System.out.println(objectMapper.writeValueAsString(specificationResult));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
Long endTime = new Date().getTime();
System.out.println(endTime - startTime);
}
}
This example is quite similar with the NestObject.class
, the most different place is we change the datafetcher behavior from return the data result directly into return the CompletableFuture
GraphQL support get the value asynchronized by returning the CompletableFuture in each Datafetcher. So think about that we have several fields and want to get the value concurrently, then we can use
Completable Future to make this happen. So the result of the example above will be:
i am in patent_biblio
i am in patent_biblio_legal
{"data":{"patent_biblio":{"id":"1111111","pn":"IL168994A","apno":"IL168994","ans":{"name":"ChrisYe","lang":"EN"},"familyType":"INPADOC"},"legal":{"legalStatus":["1","2"]}}}
7736
Please notice that, the sentence i am in patent_biblio
& i am in patent_biblio_legal
will display at the same time which means GraphQL get the result concurrently.
Batch data loader & Validation
package com.chris.graphql;
import com.chris.graphql.entity.AWSCredential;
import com.chris.graphql.entity.Patent;
import com.chris.graphql.entity.Person;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dataloader.BatchLoader;
import org.dataloader.DataLoader;
import org.dataloader.DataLoaderRegistry;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.GraphQLError;
import graphql.annotations.GraphQLTypeResolver;
import graphql.execution.ExecutionPath;
import graphql.execution.instrumentation.ChainedInstrumentation;
import graphql.execution.instrumentation.Instrumentation;
import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation;
import graphql.execution.instrumentation.fieldvalidation.FieldAndArguments;
import graphql.execution.instrumentation.fieldvalidation.FieldValidation;
import graphql.execution.instrumentation.fieldvalidation.FieldValidationEnvironment;
import graphql.execution.instrumentation.fieldvalidation.FieldValidationInstrumentation;
import graphql.execution.instrumentation.fieldvalidation.SimpleFieldValidation;
import graphql.execution.instrumentation.tracing.TracingInstrumentation;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
/**
* Created by ye830 on 12/21/2017.
*/
public class BatchTest {
public static void main(String... args) {
BatchLoader<String, Object> patentBatchLoader = keys -> CompletableFuture.supplyAsync(() -> getPatentByPatentIds(keys));
DataLoader<String, Object> patentDataLoader = new DataLoader(patentBatchLoader);
DataFetcher patentDataFetcher = env -> patentDataLoader.load(env.getArgument("patentId"));
DataFetcher offsetDataFetcher = env -> {
AWSCredential awsCredential = env.getContext();
System.out.println(awsCredential.getFoo());
Integer offset = env.getArgument("offset");
Integer limit = env.getArgument("limit");
return offset + limit;
};
DataFetcher totalDataFetcher = env -> {
String patentId = env.getArgument("patentId");
return getTotalCountByPatentId(patentId);
};
DataFetcher patentCitationDataFetcher = env -> {
if (env.getSource() != null) {
Patent patent = env.getSource();
return patentDataLoader.loadMany(getPatentCitationIds(patent.getId()));
}
return Collections.EMPTY_LIST;
};
Long startTime = new Date().getTime();
// Define the schema
File schemaFile = new File(DataLoader.class.getClassLoader().getResource("dataloader.graphqls").getPath());
// Define the type definition registry
TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(schemaFile);
// Define the runtime wiring
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder
.dataFetcher("patent", patentDataFetcher)
.dataFetcher("offset", offsetDataFetcher)
.dataFetcher("total", totalDataFetcher)
)
.type("Patent", builder -> builder
.dataFetcher("citations", patentCitationDataFetcher))
.build();
// Define the graphQL schema
GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
// Define data loader instrumentation
DataLoaderRegistry registry = new DataLoaderRegistry();
registry.register("patent", patentDataLoader);
DataLoaderDispatcherInstrumentation dataLoaderDispatcherInstrumentation = new DataLoaderDispatcherInstrumentation(registry);
// Define field validation instrumentation
ExecutionPath fieldPath = ExecutionPath.parse("/patent");
FieldValidation fieldValidation = new SimpleFieldValidation()
.addRule(fieldPath, (fieldAndArguments, fieldValidationEnvironment) -> {
Integer offset = fieldAndArguments.getArgumentValue("offset");
if(offset > 1000){
return Optional.of(fieldValidationEnvironment.mkError("offset should less equal 1000", fieldAndArguments));
}
return Optional.empty();
});
FieldValidationInstrumentation fieldValidationInstrumentation = new FieldValidationInstrumentation(fieldValidation);
TracingInstrumentation tracingInstrumentation = new TracingInstrumentation();
// Define the instrumentation chain
List<Instrumentation> chainedList = new ArrayList<>();
chainedList.add(dataLoaderDispatcherInstrumentation);
chainedList.add(fieldValidationInstrumentation);
chainedList.add(tracingInstrumentation);
ChainedInstrumentation chainedInstrumentation = new ChainedInstrumentation(chainedList);
// Define the graphQL
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema)
.instrumentation(chainedInstrumentation)
.build();
// Define the execution
//String queryString = "query Query($patentId: String!,$offset: Int,$limit: Int){patent(patentId: $patentId, offset: $offset, limit: $limit) {id pn my_name person {name} citations{id pn citations{id pn citations{id pn}}}} total(patentId: $patentId) offset(offset: $offset, limit: $limit)}";
String queryString = "query Query($patentId: String!, $offset: Int, $limit: Int) {\n" +
" patent(patentId: $patentId, offset: $offset, limit: $limit) {\n" +
" id\n" +
" pn\n" +
" my_name\n" +
" person {\n" +
" name\n" +
" }\n" +
" citations {\n" +
" ...citationPatent\n" +
" citations {\n" +
" ...citationPatent\n" +
" citations {\n" +
" ...citationPatent\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" total(patentId: $patentId)\n" +
" offset(offset: $offset, limit: $limit)\n" +
"}\n" +
"\n" +
"fragment citationPatent on Patent {\n" +
" id\n" +
" pn\n" +
"}";
Map<String, Object> variableMap = new HashMap<>();
variableMap.put("patentId", "1");
variableMap.put("offset", "1000");
variableMap.put("limit", "10");
// Define the context, for example: AWS Credential
AWSCredential awsCredential = new AWSCredential("foo", "bar");
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(queryString)
.context(awsCredential)
.operationName("Query")
.variables(variableMap)
.build();
ExecutionResult executionResult = graphQL.execute(executionInput);
Map<String, Object> specificationResult = executionResult.toSpecification();
ObjectMapper objectMapper = new ObjectMapper();
try {
System.out.println(objectMapper.writeValueAsString(specificationResult));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
Long endTime = new Date().getTime();
System.out.println("Total Cost: " + (endTime - startTime) + " ms");
}
static Map<String, List<String>> patentCitationIdMap = new HashMap();
static {
patentCitationIdMap.put("1", Arrays.asList(new String[]{"2", "3", "4"}));
patentCitationIdMap.put("2", Arrays.asList(new String[]{"1", "3", "4"}));
patentCitationIdMap.put("3", Arrays.asList(new String[]{"2", "3", "4"}));
patentCitationIdMap.put("4", Arrays.asList(new String[]{"1", "2", "3"}));
}
static List<String> getPatentCitationIds(String patentId) {
return patentCitationIdMap.get(patentId);
}
static List<Object> getPatentByPatentIds(List<String> patentIds) {
List<Object> patentCitations = patentIds.stream().map(id -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Patent patent = new Patent();
patent.setId(id);
patent.setPn("AAA");
patent.setMyName("Patent Demo");
Person person = new Person();
person.setName("Chris");
patent.setPerson(person);
return patent;
}).collect(Collectors.toList());
return patentCitations;
}
static Integer getTotalCountByPatentId(String patentId){
return 100;
}
}
Sometimes, we need to get the data in the batch model, for example, we need to get one patent, and this patent have many leaves patents, so in this situation,
we could use the Dataloader
on the DataFetcher. The result of the example above could be:
foo
{"data":{"patent":{"id":"1","pn":"AAA","my_name":null,"person":{"name":"Chris"},"citations":[{"id":"2","pn":"AAA","citations":[{"id":"1","pn":"AAA","citations":[{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]},{"id":"3","pn":"AAA","citations":[{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]},{"id":"4","pn":"AAA","citations":[{"id":"1","pn":"AAA"},{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"}]}]},{"id":"3","pn":"AAA","citations":[{"id":"2","pn":"AAA","citations":[{"id":"1","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]},{"id":"3","pn":"AAA","citations":[{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]},{"id":"4","pn":"AAA","citations":[{"id":"1","pn":"AAA"},{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"}]}]},{"id":"4","pn":"AAA","citations":[{"id":"1","pn":"AAA","citations":[{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]},{"id":"2","pn":"AAA","citations":[{"id":"1","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]},{"id":"3","pn":"AAA","citations":[{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]}]}]},"total":100,"offset":1010},"extensions":{"tracing":{"version":1,"startTime":"2018-01-06T12:14:39.764Z","endTime":"2018-01-06T12:14:49.731Z","duration":9978962056,"parsing":{"startOffset":27126149,"duration":10353775},"validation":{"startOffset":56870281,"duration":28084143},"execution":{"resolvers":[{"path":["total"],"parentType":"Query","returnType":"Int","fieldName":"total","startOffset":5651396258,"duration":80592},{"path":["offset"],"parentType":"Query","returnType":"Int","fieldName":"offset","startOffset":5654907323,"duration":290279},{"path":["patent"],"parentType":"Query","returnType":"Patent","fieldName":"patent","startOffset":5640733240,"duration":1099040620},{"path":["patent","id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":6756011623,"duration":1500992},{"path":["patent","pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":6757782837,"duration":76216},{"path":["patent","my_name"],"parentType":"Patent","returnType":"String","fieldName":"my_name","startOffset":6758077127,"duration":1296410},{"path":["patent","person"],"parentType":"Patent","returnType":"Person","fieldName":"person","startOffset":6759695543,"duration":75122},{"path":["patent","person","name"],"parentType":"Person","returnType":"String","fieldName":"name","startOffset":6759985093,"duration":68923},{"path":["patent","citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":6760451873,"duration":3042131320},{"path":["patent","citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9808188936,"duration":130552},{"path":["patent","citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9808540115,"duration":76581},{"path":["patent","citations",0,"citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":9808802315,"duration":188535},{"path":["patent","citations",0,"citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9809279671,"duration":56524},{"path":["patent","citations",0,"citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9809511967,"duration":47043},{"path":["patent","citations",0,"citations",0,"citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":9810061893,"duration":92991},{"path":["patent","citations",0,"citations",0,"citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9814951784,"duration":156809},{"path":["patent","citations",0,"citations",0,"citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9815470348,"duration":72934},{"path":["patent","citations",0,"citations",0,"citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9816725185,"duration":627966},{"path":["patent","citations",0,"citations",0,"citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9819106860,"duration":222814},{"path":["patent","citations",0,"citations",0,"citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9819789162,"duration":71475},{"path":["patent","citations",0,"citations",0,"citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9820038597,"duration":109402},{"path":["patent","citations",0,"citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9821130062,"duration":101743},{"path":["patent","citations",0,"citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9821394084,"duration":47043},{"path":["patent","citations",0,"citations",1,"citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":9821611794,"duration":77675},{"path":["patent","citations",0,"citations",1,"citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9823231668,"duration":357744},{"path":["patent","citations",0,"citations",1,"citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9824039053,"duration":116330},{"path":["patent","citations",0,"citations",1,"citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9824609400,"duration":84604},{"path":["patent","citations",0,"citations",1,"citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9824909525,"duration":98462},{"path":["patent","citations",0,"citations",1,"citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9825267269,"duration":67829},{"path":["patent","citations",0,"citations",1,"citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9826787952,"duration":91168},{"path":["patent","citations",0,"citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9831843040,"duration":143316},{"path":["patent","citations",0,"citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9832200419,"duration":66735},{"path":["patent","citations",0,"citations",2,"citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":9832735028,"duration":630154},{"path":["patent","citations",0,"citations",2,"citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9833677706,"duration":63453},{"path":["patent","citations",0,"citations",2,"citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9833886663,"duration":264388},{"path":["patent","citations",0,"citations",2,"citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9834603974,"duration":54701},{"path":["patent","citations",0,"citations",2,"citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9834968646,"duration":54336},{"path":["patent","citations",0,"citations",2,"citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9835346082,"duration":47043},{"path":["patent","citations",0,"citations",2,"citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9835549934,"duration":47407},{"path":["patent","citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9835967119,"duration":816866},{"path":["patent","citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9837155950,"duration":66735},{"path":["patent","citations",1,"citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":9837411586,"duration":78040},{"path":["patent","citations",1,"citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9837758389,"duration":3663498},{"path":["patent","citations",1,"citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9841666947,"duration":476626},{"path":["patent","citations",1,"citations",0,"citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":9842329556,"duration":75487},{"path":["patent","citations",1,"citations",0,"citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9842690217,"duration":62724},{"path":["patent","citations",1,"citations",0,"citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9843400234,"duration":170302},{"path":["patent","citations",1,"citations",0,"citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9844302798,"duration":67464},{"path":["patent","citations",1,"citations",0,"citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9844866217,"duration":150245},{"path":["patent","citations",1,"citations",0,"citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9846153875,"duration":117789},{"path":["patent","citations",1,"citations",0,"citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9846488644,"duration":42667},{"path":["patent","citations",1,"citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9846870456,"duration":39384},{"path":["patent","citations",1,"citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9847053886,"duration":35738},{"path":["patent","citations",1,"citations",1,"citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":9847243516,"duration":68558},{"path":["patent","citations",1,"citations",1,"citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9847528689,"duration":38656},{"path":["patent","citations",1,"citations",1,"citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9847685498,"duration":30633},{"path":["patent","citations",1,"citations",1,"citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9847936393,"duration":106484},{"path":["patent","citations",1,"citations",1,"citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9848816712,"duration":53607},{"path":["patent","citations",1,"citations",1,"citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9849164245,"duration":49960},{"path":["patent","citations",1,"citations",1,"citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9849335276,"duration":34279},{"path":["patent","citations",1,"citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9850513167,"duration":49231},{"path":["patent","citations",1,"citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9850688210,"duration":48501},{"path":["patent","citations",1,"citations",2,"citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":9850880392,"duration":81687},{"path":["patent","citations",1,"citations",2,"citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9851231207,"duration":136388},{"path":["patent","citations",1,"citations",2,"citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9852709224,"duration":66370},{"path":["patent","citations",1,"citations",2,"citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9853526455,"duration":75851},{"path":["patent","citations",1,"citations",2,"citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9853759115,"duration":44855},{"path":["patent","citations",1,"citations",2,"citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9854280232,"duration":65641},{"path":["patent","citations",1,"citations",2,"citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9865397267,"duration":80228},{"path":["patent","citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9866131353,"duration":53971},{"path":["patent","citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9866304937,"duration":37196},{"path":["patent","citations",2,"citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":9866667421,"duration":727886},{"path":["patent","citations",2,"citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9869150110,"duration":66735},{"path":["patent","citations",2,"citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9869392617,"duration":39385},{"path":["patent","citations",2,"citations",0,"citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":9869612879,"duration":91533},{"path":["patent","citations",2,"citations",0,"citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9870719659,"duration":63818},{"path":["patent","citations",2,"citations",0,"citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9870908195,"duration":33185},{"path":["patent","citations",2,"citations",0,"citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9878985322,"duration":114871},{"path":["patent","citations",2,"citations",0,"citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9897527814,"duration":103932},{"path":["patent","citations",2,"citations",0,"citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9897913637,"duration":41573},{"path":["patent","citations",2,"citations",0,"citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9898777181,"duration":55431},{"path":["patent","citations",2,"citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9899199837,"duration":55794},{"path":["patent","citations",2,"citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9899377797,"duration":34643},{"path":["patent","citations",2,"citations",1,"citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":9899598059,"duration":88980},{"path":["patent","citations",2,"citations",1,"citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9899884326,"duration":38291},{"path":["patent","citations",2,"citations",1,"citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9900031654,"duration":32821},{"path":["patent","citations",2,"citations",1,"citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9900275984,"duration":39385},{"path":["patent","citations",2,"citations",1,"citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9900421124,"duration":30633},{"path":["patent","citations",2,"citations",1,"citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9900621694,"duration":40114},{"path":["patent","citations",2,"citations",1,"citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9900751882,"duration":29538},{"path":["patent","citations",2,"citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9900993660,"duration":31361},{"path":["patent","citations",2,"citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9901143540,"duration":35373},{"path":["patent","citations",2,"citations",2,"citations"],"parentType":"Patent","returnType":"[Patent]","fieldName":"citations","startOffset":9901285762,"duration":55795},{"path":["patent","citations",2,"citations",2,"citations",0,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9901484144,"duration":31726},{"path":["patent","citations",2,"citations",2,"citations",0,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9901599745,"duration":28809},{"path":["patent","citations",2,"citations",2,"citations",1,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9901780622,"duration":31727},{"path":["patent","citations",2,"citations",2,"citations",1,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9901928315,"duration":32091},{"path":["patent","citations",2,"citations",2,"citations",2,"id"],"parentType":"Patent","returnType":"String!","fieldName":"id","startOffset":9902142013,"duration":32820},{"path":["patent","citations",2,"citations",2,"citations",2,"pn"],"parentType":"Patent","returnType":"String!","fieldName":"pn","startOffset":9902261625,"duration":31727}]}}}}
Total Cost: 11627 ms
May be you can’t catch the point of why we need to do this, but once if we compare the time cost between this example result and the normal way to get the result, then we could find a huge different So let’s change the datafetcher into the normal way
package com.chris.graphql;
import com.chris.graphql.entity.Patent;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dataloader.BatchLoader;
import org.dataloader.DataLoader;
import org.dataloader.DataLoaderRegistry;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
/**
* Created by ye830 on 12/21/2017.
*/
public class SingleTest {
public static void main(String... args) {
DataFetcher patentDataFetcher = env -> getPatentByPatentIds(Arrays.asList(((String)env.getArgument("patentId")))).get(0);
DataFetcher patentCitationDataFetcher = env -> {
if(env.getSource()!=null){
Patent patent = env.getSource();
List<String> patentIds = getPatentCitationIds(patent.getId());
return getPatentByPatentIds(patentIds);
}
return Collections.EMPTY_LIST;
};
Long startTime = new Date().getTime();
// Define the schema
File schemaFile = new File(DataLoader.class.getClassLoader().getResource("dataloader.graphqls").getPath());
// Define the type definition registry
TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(schemaFile);
// Define the runtime wiring
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder
.dataFetcher("patent", patentDataFetcher)
)
.type("Patent", builder -> builder
.dataFetcher("citations", patentCitationDataFetcher))
.build();
// Define the graphQL schema
GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
// Define the graphQL
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema)
.build();
// Define the execution
String queryString = "query Query($patentId: String!){patent(patentId: $patentId) {id pn citations{id pn citations{id pn citations{id pn}}}}}";
Map<String, Object> variableMap = new HashMap<>();
variableMap.put("patentId", "1");
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(queryString)
.operationName("Query")
.variables(variableMap)
.build();
ExecutionResult executionResult = graphQL.execute(executionInput);
Map<String, Object> specificationResult = executionResult.toSpecification();
ObjectMapper objectMapper = new ObjectMapper();
try {
System.out.println(objectMapper.writeValueAsString(specificationResult));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
Long endTime = new Date().getTime();
System.out.println("Total Cost: "+(endTime - startTime)+" ms");
}
static Map<String, List<String>> patentCitationIdMap = new HashMap();
static{
patentCitationIdMap.put("1", Arrays.asList(new String[]{"2","3","4"}));
patentCitationIdMap.put("2", Arrays.asList(new String[]{"1","3","4"}));
patentCitationIdMap.put("3", Arrays.asList(new String[]{"2","3","4"}));
patentCitationIdMap.put("4", Arrays.asList(new String[]{"1","2","3"}));
}
static List<String> getPatentCitationIds(String patentId){
return patentCitationIdMap.get(patentId);
}
static List<Object> getPatentByPatentIds(List<String> patentIds){
List<Object> patentCitations = patentIds.stream().map(id -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Patent patent = new Patent();
patent.setId(id);
patent.setPn("AAA");
return patent;
}).collect(Collectors.toList());
return patentCitations;
}
}
then the total time cost could increase to nearly 5 seconds.
{"data":{"patent":{"id":"1","pn":"AAA","citations":[{"id":"2","pn":"AAA","citations":[{"id":"1","pn":"AAA","citations":[{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]},{"id":"3","pn":"AAA","citations":[{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]},{"id":"4","pn":"AAA","citations":[{"id":"1","pn":"AAA"},{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"}]}]},{"id":"3","pn":"AAA","citations":[{"id":"2","pn":"AAA","citations":[{"id":"1","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]},{"id":"3","pn":"AAA","citations":[{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]},{"id":"4","pn":"AAA","citations":[{"id":"1","pn":"AAA"},{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"}]}]},{"id":"4","pn":"AAA","citations":[{"id":"1","pn":"AAA","citations":[{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]},{"id":"2","pn":"AAA","citations":[{"id":"1","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]},{"id":"3","pn":"AAA","citations":[{"id":"2","pn":"AAA"},{"id":"3","pn":"AAA"},{"id":"4","pn":"AAA"}]}]}]}}}
Total Cost: 49478 ms
Using annotation to define the schema
Imagine that we need to define a large graphql schema which so many types need to be included. So we also need to add a lot of beans map to the schema, so may be we can put these two together. Let’s use graphql-java-annotation
to make this happen.
Maven dependency
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-annotations</artifactId>
<version>3.0.3</version>
</dependency>
Value Object
package com.chris.graphql.entity;
import com.chris.graphql.datafetcher.TeamNameDataFetcher;
import graphql.annotations.GraphQLDataFetcher;
import graphql.annotations.GraphQLField;
import graphql.annotations.GraphQLName;
/**
* Created by ye830 on 12/22/2017.
*/
@GraphQLName("team")
public class Team {
@GraphQLName("team_name")
@GraphQLField
@GraphQLDataFetcher(TeamNameDataFetcher.class)
public
String name;
@GraphQLName("team_total_members")
@GraphQLField
public String total;
}
Test class
package com.chris.graphql;
import com.chris.graphql.entity.Team;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.annotations.GraphQLAnnotations;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
/**
* Created by ye830 on 12/22/2017.
*/
public class AnnotationTest {
public static void main(String... args) {
// Programmatically
GraphQLObjectType queryType = GraphQLAnnotations.object(Team.class);
//Query by
GraphQLSchema graphQLSchema = GraphQLSchema.newSchema()
.query(queryType)
.build();
GraphQL build = GraphQL.newGraphQL(graphQLSchema).build();
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.operationName("team")
.query("query team{team_name}")
.context(new Team())
.root(new Team())
.build();
ExecutionResult executionResult = build.execute(executionInput);
//ExecutionResult executionResult = build.execute("query team{team_name}", new Team());
System.out.println(executionResult.getData().toString());
}
}
All these example are on Github, please check https://github.com/ye8303019/project-graphql-java-demo/tree/master to get the complete source code.
OK, that’s all the regular test for GraphQL, next step, let’s integrate GraphQL with Spring. Imagine that we want to provide the GraphQL service vai HTTP/HTTPS. and also we may think if we could find some useful starter to help us make the code more elegant.
Integration with spring boot
In this example, let’s using the spring-boot-starter
& spring-boot-starter-web
to build a microservice.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chris.graphql</groupId>
<artifactId>project-graphql</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>project-graphql Maven Webapp</name>
<url>http://maven.apache.org</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.4.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.boot.version>1.4.4.RELEASE</spring.boot.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>6.0</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>project-graphql</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<optimize>true</optimize>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
graphql file
type Query {
patent(patentId: String, offset: Int = 0, limit: Int = 10): Patent
total(patentId: String): Int
offset(offset: Int = 0, limit: Int = 10): Int
}
type Patent {
id: String!
pn: String!
my_name: String
apno: String
citations: [Patent]
person: Person
}
type Person {
name: String
}
GraphQL Bean
package com.chris.graphql.dataloader;
import com.chris.graphql.entity.Patent;
import org.dataloader.BatchLoader;
import org.dataloader.DataLoader;
import org.dataloader.DataLoaderRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import graphql.GraphQL;
import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
/**
* Created by ye830 on 12/21/2017.
*/
@Configuration
public class CitationDataLoader {
@Bean("CitationGraphQL")
protected GraphQL getGraphQL() {
BatchLoader<String, Object> patentBatchLoader = keys -> CompletableFuture.supplyAsync(() -> getPatentByPatentIds(keys));
DataLoader<String, Object> patentDataLoader = new DataLoader(patentBatchLoader);
DataFetcher patentDataFetcher = env -> patentDataLoader.load(env.getArgument("patentId"));
DataFetcher patentCitationDataFetcher = env -> {
if (env.getSource() != null) {
Patent patent = env.getSource();
return patentDataLoader.loadMany(getPatentCitationIds(patent.getId()));
}
return Collections.EMPTY_LIST;
};
Long startTime = new Date().getTime();
// Define the schema
File schemaFile = new File(DataLoader.class.getClassLoader().getResource("dataloader.graphqls").getPath());
// Define the type definition registry
TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(schemaFile);
// Define the runtime wiring
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder
.dataFetcher("patent", patentDataFetcher)
)
.type("Patent", builder -> builder
.dataFetcher("citations", patentCitationDataFetcher))
.build();
// Define the graphQL schema
GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
// Define data loader registry
DataLoaderRegistry registry = new DataLoaderRegistry();
registry.register("patent", patentDataLoader);
// Define the graphQL
return GraphQL.newGraphQL(graphQLSchema)
.instrumentation(new DataLoaderDispatcherInstrumentation(registry))
.build();
}
static Map<String, List<String>> patentCitationIdMap = new HashMap();
static {
patentCitationIdMap.put("1", Arrays.asList(new String[]{"2", "3", "4"}));
patentCitationIdMap.put("2", Arrays.asList(new String[]{"1", "3", "4"}));
patentCitationIdMap.put("3", Arrays.asList(new String[]{"2", "3", "4"}));
patentCitationIdMap.put("4", Arrays.asList(new String[]{"1", "2", "3"}));
}
private List<String> getPatentCitationIds(String patentId) {
return patentCitationIdMap.get(patentId);
}
private List<Object> getPatentByPatentIds(List<String> patentIds) {
List<Object> patentCitations = patentIds.stream().map(id -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Patent patent = new Patent();
patent.setId(id);
patent.setPn("AAA");
return patent;
}).collect(Collectors.toList());
return patentCitations;
}
}
Controller
package com.chris.graphql.controller;
import com.chris.graphql.entity.QueryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
/**
* Created by ye830 on 9/11/2017.
*/
@RestController
@RequestMapping(value = "/patent")
public class PatentController {
@Autowired
GraphQL citationGraphQL;
@RequestMapping(
value = "/citation",
method = RequestMethod.POST,
produces = "application/json"
)
public Map<String, Object> citation(@RequestBody QueryBean queryBean){
Long startTime = new Date().getTime();
// Define the execution
String queryString = queryBean.getQuery();
Map<String, Object> variableMap = new HashMap<>();
for(Map.Entry<String, Object> entry : queryBean.getVariable().entrySet()){
variableMap.put(entry.getKey(), entry.getValue());
}
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(queryString)
.operationName("Query")
.variables(variableMap)
.build();
ExecutionResult executionResult = citationGraphQL.execute(executionInput);
Map<String, Object> specificationResult = executionResult.toSpecification();
Long endTime = new Date().getTime();
System.out.println("Total Cost: "+(endTime - startTime)+" ms");
return specificationResult;
}
}
So we could get the result by send post request to the server
Integration with GraphQL spring boot starter & GraphiQL Spring starter
Spring hava provide us a very useful starter for GraphQL, we just need to add the graphql-spring-boot-starter
, graphiql-spring-boot-starter
, graphql-java-tools
dependency and write the bean and add some configurations then every thing will set up by spring
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chris.graphql</groupId>
<artifactId>project-graphql-spring</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.4.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<spring.boot.version>1.4.4.RELEASE</spring.boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>6.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-annotations</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>3.9.2</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>3.9.2</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>4.3.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<optimize>true</optimize>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
application.properties
graphql.servlet.corsEnabled=true
graphql.servlet.mapping=/graphql
graphql.servlet.enabled=true
graphiql.enabled=true
graphiql.endpoint=/graphql
graphiql.mapping=/graphiql
graphql files
type Query {
# Get patent information by a patent id
patent(patent_id: String):Patent
}
type Patent {
# Patent unique id
id: String
# Patent number
pn: String
# A list of patent IPC code
ipc: [String]
# The Assignee of the patent
assignee: Assignee
# A list of inventors of the patent
inventor: [Inventor]
}
interface Person {
# Person name
name: String
# Person age
age: Int
# Person address
address: String
}
# The assignee of the patent
type Assignee implements Person {
# The name of the assignee
name: String
# The age of the assignee
age: Int
# The address of the assignee
address: String
# The normalized name of the assignee
nname: String
}
# The inventor of the patent
type Inventor implements Person {
# The name of the inventor
name: String
# The age of the inventor
age: Int
# The address of the inventor
address: String
# The country of the inventor
country: String
}
For the java codes please check my github repository project-graphql-spring-demo
Reference:
[1]:http://facebook.github.io/graphql/October2016/
[2]:http://graphql.org/
[3]:https://developer.github.com/v4/
[4]:https://githubengineering.com/the-github-graphql-api/
[5]:http://graphql.org/graphql-js/mutations-and-input-types/
[6]:http://www.baeldung.com/graphql
_[7]:https://github.com/graphql-java/graphql-spring-boot
_[8]:https://github.com/graphql-java/graphql-java-tools
_[9]:https://github.com/graphql-java/graphql-java-servlet
_[10]:https://github.com/apottere/awesome-graphql-java
_[11]:https://github.com/apottere/awesome-graphql