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.
- 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:
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. |