Enterprise Java

Supersonic Subatomic GraphQL

MicroProfile GraphQL is now included in the just released version 1.5.0 of Quarkus.

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>

NOTE: 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 quarkus-resteasy dependency as we do not need JAX-RS.

Your first GraphQL Endpoint.

Let’s change the ExampleResource Rest service to be a GraphQL endpoint.

  1. Replace the @Path("/hello") class annotation with @GraphQLApi.
  2. Replace the @GET method annotation with @Query.
  3. Remove the @Produces(MediaType.TEXT_PLAIN) method annotation and all JAX-RS imports.

That is it! Your ExampleResource should look like this now:

01
02
03
04
05
06
07
08
09
10
11
12
13
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:

1
mvn quarkus:dev

Now browse to localhost:8080/graphql-ui/ and run the following query:

1
2
3
{
  hello
}

This will return:

1
2
3
4
5
{
  "data": {
    "hello": "hello"
  }
}

Also see the Quarkus 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:

1
mvn clean install

Now browse to the quarkus example:

1
cd quarkus-example

Look at ProfileGraphQLApi.java that is marked as a @GraphQLApi:

1
2
3
4
@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:

1
mvn quarkus:dev

Now browse to localhost:8080/graphql-ui/ and run the following query:

01
02
03
04
05
06
07
08
09
10
{
  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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
{
  person1: person(personId:1){
    names
    surname
    scores{
      name
      value
    }
  }
  person2: person(personId:2){
    names
    surname
  }
}

This will return :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
  "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:

1
2
3
4
5
6
7
8
@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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
  "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
    }
  }
}

Native mode

Let’s run this example in native mode (use graalvm-ce-java11-19.3.2):

1
mvn -Pnative clean install

This will create a native executable and will now start the application very quickly:

1
./target/quarkus-example-1.0.0-SNAPSHOT-runner

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:

01
02
03
04
05
06
07
08
09
10
// 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:

1
2
3
4
5
<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:

1
2
3
4
5
6
@GraphQlClientApi
public interface PersonGraphQLClient {
 
    public Person person(int personId);
 
}

And now we can use this:

1
2
3
4
5
6
//@Inject
    //PersonGraphQLClient personClient; or
    PersonGraphQLClient personClient = GraphQlClientBuilder.newBuilder().build(PersonGraphQLClient.class);
 
    // ...
    Person person = personClient.person(id);

Running the Quarkus client app we can now make a call to the server (make sure this is still running) and print the response:

1
java -jar target/quarkus-client-1.0.0-SNAPSHOT-runner.jar 2

The number (2) is the personId in our example:

Summary

This is a short and quick introduction to MicroProfile GraphQL that is now available in Quarkus. There are many more features and even more planned, so stay tuned.

Published on Java Code Geeks with permission by Phillip Krüger, partner at our JCG program. See the original article here: Supersonic Subatomic GraphQL

Opinions expressed by Java Code Geeks contributors are their own.

Phillip Kruger

Phillip is a software developer and a systems architect who knacks for solving problems. He has a passion for clean code and evolutionary architecture. He blogs about all technical things.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button