Supersonic Subatomic GraphQL
MicroProfile GraphQL is now included in the just released version 1.5.0 of Quarkus.
What is GraphQL?
"GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. GraphQL interprets strings from the client, and returns data in an understandable, predictable, pre-defined manner. GraphQL is an alternative, though not necessarily a replacement for REST."
Read the full GraphQL Specification
Why GraphQL ?
The main reasons for using GraphQL are:
-
Avoiding over-fetching or under-fetching data. Clients can retrieve several types of data in a single request or can limit the response data based on specific criteria.
-
Enabling data models to evolve. Changes to the schema can be made so as to not require changes on existing clients, and vice versa - this can be done without a need for a new version of the application.
-
Advanced developer experience:
-
The schema defines how the data can be accessed and serves as the contract between the client and the server. Development teams on both sides can work without further communication.
-
Native schema introspection enables users to discover APIs and to refine the queries on the client-side. This advantage is increased with graphical tools such as GraphiQL and GraphQL Voyager enabling smooth and easy API discovery.
-
On the client-side, the query language provides flexibility and efficiency enabling developers to adapt to the constraints of their technical environments while reducing server round-trips.
-
What is MicroProfile GraphQL?
"The intent of the MicroProfile GraphQL specification is to provide a "code-first" set of APIs that will enable users to quickly develop portable GraphQL-based applications in Java. There are 2 main requirements for all implementations of this specification, namely:
Generate and make the GraphQL Schema available. This is done by looking at the annotations in the users code, and must include all GraphQL Queries and Mutations as well as all entities as defined implicitly via the response type or argument(s) of Queries and Mutations.
Execute GraphQL requests. This will be in the form of either a Query or a Mutation. As a minimum the specification must support executing these requests via HTTP."
Read the full MicroProfile GraphQL Specification
You can now use code.quarkus.io to get going with Quarkus and include the SmallRye GraphQL Extension.
This will create a Quarkus starter application with the following dependencies:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-graphql</artifactId>
</dependency>
At the moment, the example application created is a JAX-RS application. There is some work in progress to allow extensions
to define custom examples application, but until then we always get a JAX-RS application. You can remove the |
Your first GraphQL Endpoint.
Let’s change the ExampleResource
Rest service to be a GraphQL endpoint.
-
Replace the
@Path("/hello")
class annotation with@GraphQLApi
. -
Replace the
@GET
method annotation with@Query
. -
Remove the
@Produces(MediaType.TEXT_PLAIN)
method annotation and all JAX-RS imports.
That is it! Your ExampleResource
should look like this now:
package org.acme;
import org.eclipse.microprofile.graphql.GraphQLApi;
import org.eclipse.microprofile.graphql.Query;
@GraphQLApi
public class ExampleResource {
@Query
public String hello() {
return "hello";
}
}
You can now run the application using Quarkus dev mode:
mvn quarkus:dev
Now browse to localhost:8080/graphql-ui/ and run the following query:
{
hello
}
This will return:
{
"data": {
"hello": "hello"
}
}
Also see the MicroProfile GraphQL guide.
A more detailed example
Let’s look at a more detailed example, get the source from this GitHub project.
This is a multi-module application. First compile all modules. In the root:
mvn clean install
Now browse to the quarkus example:
cd quarkus-example
Look at ProfileGraphQLApi.java
that is marked as a @GraphQLApi
:
@Query("person")
public Person getPerson(@Name("personId") int personId){
return personDB.getPerson(personId);
}
Above method will get a person by personId
. As you can see the method is made queryable with the @Query
annotation. You can optionally provide the name ("person" in this case),
however the default would be "person" anyway (method name without "get"). You can also optionally name the parameter, but the default would be the parameter name ("personId").
The Person Object is a POJO that represents a Person (User or Member) in the system. It has many fields, some that are other complex POJOs:
However, the Query
annotation makes it possible to query the exact fields we are interested in.
Run the example application:
mvn quarkus:dev
Now browse to localhost:8080/graphql-ui/ and run the following query:
{
person(personId:1){
names
surname
scores{
name
value
}
}
}
Notice that you have 'code insight' in the editor. That is because GraphQL has a schema and also supports introspection.
We can request only the fields we are interested in, making the payload much smaller.
We can also combine queries, i.e., lets say we want to get the fields for person 1 as shown above, and also the name and surname for person 2, we can do the following:
{
person1: person(personId:1){
names
surname
scores{
name
value
}
}
person2: person(personId:2){
names
surname
}
}
This will return :
{
"data": {
"person1": {
"names": [
"Christine",
"Fabian"
],
"surname": "O'Reilly",
"scores": [
{
"name": "Driving",
"value": 15
},
{
"name": "Fitness",
"value": 94
},
{
"name": "Activity",
"value": 63
},
{
"name": "Financial",
"value": 22
}
]
},
"person2": {
"names": [
"Masako",
"Errol"
],
"surname": "Zemlak"
}
}
}
Source fields
If you look closely at our query, you will see we asked for the scores
field of the person, however, the Person
POJO does not contain a scores
field.
We added the scores
field by adding a @Source
field to the person:
@Query("person")
public Person getPerson(@Name("personId") int personId){
return personDB.getPerson(personId);
}
public List<Score> getScores(@Source Person person) {
return scoreDB.getScores(person.getIdNumber());
}
So we can add fields that merge onto the output by adding the @Source
parameter that matches the response type.
Partial results
The above example merges two different data sources, but let’s say the score system is down. We will then still return the data we have, and an error for the score:
{
"errors": [
{
"message": "Scores for person [797-95-4822] is not available",
"locations": [
{
"line": 5,
"column": 5
}
],
"path": [
"person",
"scores2"
],
"extensions": {
"exception": "com.github.phillipkruger.user.graphql.ScoresNotAvailableException",
"classification": "DataFetchingException"
}
}
],
"data": {
"person": {
"names": [
"Christine",
"Fabian"
],
"surname": "O'Reilly",
"scores2": null
}
}
}
ネイティブモード
Let’s run this example in native mode (using graalvm-ce-java11-19.3.2):
mvn -Pnative clean install
This will create a native executable and will now start the application very quickly:
./target/quarkus-example-1.0.0-SNAPSHOT-runner
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2020-06-11 17:02:55,041 INFO [io.quarkus] (main) quarkus-example 1.0.0-SNAPSHOT native (powered by Quarkus 1.5.0.Final) started in 0.026s. Listening on: http://0.0.0.0:8080
2020-06-11 17:02:55,041 INFO [io.quarkus] (main) Profile prod activated.
2020-06-11 17:02:55,041 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb, smallrye-graphql, smallrye-openapi, swagger-ui]
In the pipeline
This is the first version of the MicroProfile GraphQL Spec and there are many things in the pipeline. One of those is a client. We are proposing two types of clients:
Dynamic
The dynamic client will allow you to build a query using a builder:
// Building of the graphql document.
Document myDocument = document(
operation(Operation.Type.QUERY,
field("people",
field("id"),
field("name")
)));
// Serialization of the document into a string, ready to be sent.
String graphqlRequest = myDocument.toString();
For more details see: github.com/worldline/dynaql
Type safe
The type safe client will be closer to MicroProfile RESTClient. Looking at the same example as above, lets see how we can to use it.
From the root of the project, browse to the quarkus-client
folder. This example uses Quarkus Command Mode to make a Query.
The client is not yet a Quarkus Extension, so we add it in our project like this:
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-graphql-client</artifactId>
<version>${smallrye-graphql.version}</version>
</dependency>
Now we can create a POJO that contains only fields that we are interested in. Looking at Person
and Score
in the client module, it is much smaller than the definition on the server side:
All we need to do now is to add an interface that defines the queries that we are interested in:
@GraphQlClientApi
public interface PersonGraphQLClient {
public Person person(int personId);
}
And now we can use this:
//@Inject
//PersonGraphQLClient personClient; or
PersonGraphQLClient personClient = GraphQlClientBuilder.newBuilder().build(PersonGraphQLClient.class);
// ...
Person person = personClient.person(id);
Running the Quarkus client appication we can now make a call to the server (make sure this is still running) and print the response:
java -jar target/quarkus-client-1.0.0-SNAPSHOT-runner.jar 2
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=lcd
=========================
| Masako Zemlak |
| |
| Driving |
| 48 |
| |
| Fitness |
| 73 |
| |
| Activity |
| 62 |
| |
| Financial |
| 54 |
| |
=========================
The number (2) is the personId
in our example: