Eclipse Vert.x meets GraphQL

I re­cently added GraphQL sup­port to Gen­tics Mesh and I thought it would be a good idea to boil down the essence of my im­ple­men­ta­tion in ex­am­ple so that I could share it in a sim­pler form. The ex­am­ple I’m about to show will not cover all as­pects that I have added to the Gen­tics Mesh API (e.g. pag­ing, search and error han­dling) but it will give you a basic overview of the parts that I put to­gether. GraphQL does not re­quire a GraphDB even if the name might sug­gest it.

Using a graphdb in com­bi­na­tion with GraphQL does nev­er­the­less pro­vide you with some ad­van­tages which I will high­light later on.

What is GraphQL? What is it good for?

GraphQL as the name sug­gests is a new query lan­guage which can be used to load ex­actly the amount of data which you ask for.

The query is de­fined in way that the query fields cor­re­late to the JSON data that is being re­trieved. In our Star­Wars Demo do­main model this query will load the name of human 1001 which is Darth Vader.

{
  vader: human(id: 1001) {
    name
  }
}

Would re­sult in:

{
  "data": {
    "vader": {
      "name": "Darth Vader"
    }
  }
}

The Demo App

The demo ap­pli­ca­tion I build makes use of the graphql-​java li­brary. The data is being stored in a graph data­base. I use Ori­entDB in com­bi­na­tion with the OGM Ferma to pro­vide a data ac­cess layer. GraphQL does not nec­es­sar­ily re­quire a graph data­base but in this ex­am­ple I will make use of one and high­light the ben­e­fits of using a GraphDB for my use­case.

You can find the sources here: https://github.com/Jotschi/vertx-​graphql-example

Data

The Star­Wars­Data class cre­ates a Graph which con­tains the Star Wars Movies and Char­ac­ters, Plan­ets and their re­la­tions. The model is fairly sim­ple. There is a sin­gle Star­Wars­Root ver­tex which acts as a start el­e­ment for var­i­ous ag­gre­ga­tion ver­tices: Movies are stored in Movie­Root, Plan­ets in Plan­et­s­Root, Char­ac­ters are stored in Hu­man­s­Root and Droid­s­Root.

The model classes are used for wrap­pers of the spe­cific graph ver­tices. The Ferma OGM is used to pro­vide these wrap­pers. Each class con­tains meth­ods which can be used to tra­verse the graph to lo­cate the needed ver­tices. The found ver­tices are in turn again wrapped and can be used to lo­cate other graph el­e­ments.

Schema

The next thing we need is the GraphQL schema. The schema de­scribes each el­e­ment which can be re­trieved. It also de­scribes the prop­er­ties and re­la­tion­ships for these el­e­ments.

The graphql-​java li­brary pro­vides an API to cre­ate the ob­ject types and schema in­for­ma­tion.

private GraphQLObjectType createMovieType() {
  return newObject().name("Movie")
    .description("One of the films in the Star Wars universe.")

    // .title
    .field(newFieldDefinition().name("title")
        .description("Title of the episode.")
        .type(GraphQLString)
        .dataFetcher((env) -> {
          Movie movie = env.getSource();
          return movie.getName();
        }))

    // .description
    .field(newFieldDefinition().name("description")
        .description("Description of the episode.")
        .type(GraphQLString))

    .build();
}

A type can be ref­er­enced via a GraphQLTypeReference once it has been cre­ated and added to the schema. This is es­pe­cially im­por­tant if you need to add fields which ref­er­ence other types. Data fetch­ers are used to ac­cess the con­text, tra­verse the graph and re­trieve prop­er­ties from graph el­e­ments.

An­other great source to learn more about the schema op­tions is the Garfield­Schema ex­am­ple.

Fi­nally all the cre­ated types must be ref­er­enced by a cen­tral ob­ject type Query­Type. The query type ob­ject is ba­si­cally the root ob­ject for the query. It de­fines what query op­tions are ini­tially pos­si­ble. In our case it is pos­si­ble to load the hero of the sage, spe­cific movies, hu­mans or droids.

Verticle

The GraphQLVer­ti­cle is used to ac­cept the GraphQL re­quest and process it.

The ver­ti­cle also con­tains a Sta­ticHan­dler to pro­vide the Graphiql Browser web in­ter­face. This in­ter­face will allow you to quickly dis­cover and ex­per­i­ment with GraphQL.

The query han­dler ac­cepts the query JSON data.

An Ori­entDB trans­ac­tion is being opened and the query is ex­e­cuted:

demoData.getGraph().asyncTx((tx) -> {
  // Invoke the query and handle the resulting JSON
  GraphQL graphQL = newGraphQL(schema).build();
  ExecutionInput input = new ExecutionInput(query, null, queryJson, demoData.getRoot(), extractVariables(queryJson));
  tx.complete(graphQL.execute(input));
}, (AsyncResult<ExecutionResult> rh) -> {
  ...
});

The ex­e­cute method ini­tially needs a con­text vari­able. This con­text passed along with the query. In our case the con­text is the root el­e­ment of the graph demoData.getRoot(). This con­text el­e­ment also serves as the ini­tial source for our data fetch­ers.

.dataFetcher((env) -> {
  StarWarsRoot root = env.getSource();
  return root.getHero();
}))

The data fetch­ers for the hero type on the other hand will be able to ac­cess the hero el­e­ment since the fetcher above re­turned the el­e­ment. Using this mech­a­nism it is pos­si­ble to tra­verse the graph. It is im­por­tant to note that each in­vo­ca­tion on the do­main model meth­ods will di­rectly ac­cess the graph data­base. This way it is pos­si­ble to in­flu­ence the graph data­base query down to the low­est level. When omit­ting a prop­erty from the graphql query it will not be loaded from the graph. Thus there is no need to write an ad­di­tional data ac­cess layer. All op­er­a­tions are di­rectly mapped to graph data­base.

The StarWarsRoot Ferma class getHero() method in turn de­fines a Tin­ker­Pop Grem­lin tra­ver­sal which is used to load the Ver­tex which rep­re­sents the hero of the Star Wars saga.

Apache Tin­ker­Pop is an open source, vendor-​agnostic, graph frame­work / API which is sup­ported by many graph data­base ven­dors. One part of Tin­ker­Pop is the Grem­lin tra­ver­sal lan­guage which is great to query graph data­bases.

...
public Droid getHero() {
  // Follow the HAS_ROOT edge and return the first found Vertex which could be found. 
  // Wrap the Vertex explicitly in the Droid Ferma class.  
  return traverse((g) -> g.out(HAS_HERO)).nextOrDefaultExplicit(Droid.class, null);
}
...

Once the query has been ex­e­cuted the re­sult han­dler is being in­voked. It con­tains some code to process the re­sult data and po­ten­tial er­rors. It is im­por­tant to note that a GraphQL query will al­ways be an­swered with a 2xx HTTP sta­tus code. If an el­e­ment which is being ref­er­enced in the query can’t be loaded an error will be added to the re­sponse JSON ob­ject.

Testing

Test­ing is fairly straight for­ward. Al­though there are mul­ti­ple ap­proaches. One ap­proach is to use unit test­ing di­rectly on the GraphQL types. An­other op­tion is to run queries against the end­point.

The GraphQL­Test class I wrote will run mul­ti­ple queries against the end­point. A Pa­ra­me­ter­ized JUnit test is used it­er­ate over the queries.

A typ­i­cal query does not only con­tain the query data. The as­ser­tions on the re­sponse JSON are di­rectly in­cluded in query using plain com­ments.

I build an As­sertJ as­ser­tion to check the com­ments of a query and ver­ify that the as­ser­tion matches the re­sponse.

  assertThat(response).compliesToAssertions(queryName);

Run the example

You can run the ex­am­ple by ex­e­cut­ing the GraphQLServer class and ac­cess the Graphiql browser on http://lo­cal­host:3000/browser/

Where to go from here?

The ex­am­ple is read-​only. GraphQL also sup­ports data mu­ta­tion which can be used to ac­tu­ally mod­ify and store data. I have not yet ex­plored that part of GraphQL but I as­sume it might not be that hard to add mu­ta­tion sup­port to the ex­am­ple.

Ad­di­tion­ally it does not cover how to ac­tu­ally make use of such API. I re­cently up­dated my Vert.x ex­am­ple which shows how to use Vert.x tem­plate han­dlers to build a small server which ren­ders some pages using data which was loaded via GraphQL.

Thanks for read­ing. If you have any fur­ther ques­tions or feed­back don’t hes­i­tate to send me a tweet to @Jotschi or @gen­tic­smesh.

Next post

TCP Client using Eclipse Vert.x, Kotlin and Gradle build

In this blog post, I demonstrate how to write a very simple TCP client that keeps a connection open to a custom-written server in cloud.

Read more
Previous post

Eclipse Vert.x 3.5.0 released!

The Vert.x team is pleased to announce the release of Eclipse Vert.x 3.5.0.

Read more
Related posts

An Introduction to the Vert.x Context Object

Under the hood, the vert.x Context class plays a critical part in maintaining the thread-safety guarantees of verticles. Most of the time, vert.x coders don't need to make use of Context objects directly.

Read more

JWT Authorization for Vert.x with Keycloak

In this blog post, you'll learn about JWT foundations, protect routes with JWT Authorization, JWT encoded tokens, and RBAC with Keycloak

Read more

Combine vert.x and mongo to build a giant

This blog post is part of the introduction to Vert.x series. We are now going to replace this JDBC client by the vertx-mongo-client, and thus connect to a Mongo database.

Read more