ICICLES OF THOUGHT

Ramblings on Web Development and Software Architecture

Posted  2 months ago

Tags

Building GraphQL APIs powered by Vert.x, Dagger, jOOQ & Kotlin - I

This is the first in a series of articles where we explore a JVM based stack comprising of Kotlin, Vert.X, Dagger and jOOQ for development of GraphQL APIs.

About GraphQL

GraphQL is an application level query language which can, as of this writing, be implemented and consumed in most popular languages.

Quoting from the official site:

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

If you are unfamiliar with GraphQL from a consumer perspective, it might be better to go through the official introduction first. This post assumes user-level familiarity.

GraphQL in Java

When it comes to JVM, graphql-java is the most mature GraphQL implementation, and is what we use in this article. However, GraphQL java cares only about the execution of a GraphQL request given a schema and associated handlers. In a realistic application we'd want to expose a GraphQL over an HTTP endpoint. So, it is common to pair graphql-java with a web framework or HTTP handler. Our choice for the latter in the context of this post is Vert.x-Web.

About Vert.X & Vert.X-Web

Vert.X is a toolkit for building event-driven reactive services and Vert.X-web leverages the lower level toolkit for building web applications.

From the official docs:

Vert.x Core

The Vert.x core APIs contain the backbone for writing Vert.x applications and low-level support for HTTP, TCP, UDP, file system, asynchronous streams and many other building blocks. It is also used by many other components of Vert.x.

Vert.x Web

A tool-kit for writing sophisticated modern web applications and HTTP microservices.

As we will see below, Vert.X and graphql-java work very well together because the graphql-java natively supports asynchronous execution strategy which fits perfectly with the asynchronous event-driven style that Vert.X facilitates.

jOOQ for persistence

A real-world application would also need to persist its data, and for that our solution of choice here is jOOQ, a type-safe SQL builder for Java.

jOOQ is commonly used in a database-first manner where the database is managed through DB centric tools and the DAO (Data Access Object) layer to interface with the database from application code is generated by introspecting the database.

The generators which come with jOOQ don't support Vert.X out of the box, but the Vert.X core is very flexible and the vertx-jooq project provides code-generation support that bridges the two.

Dagger for dependency injection

Vert.X itself is quite unopinionated regarding the organization of code and how services are integrated. It does not prescribe any specific dependency injection solution. However, from my experience of building backend applications, having a DI solution significantly helps towards testability of the application and reducing coupling among components.

Our choice of DI framework is Dagger. In contrast to many other popular DI/IoC solutions, dagger operates through compile time code generation, reducing the reflection overhead causing startup delay. We will cover dagger specific aspects in second part of this series, however for users unfamiliar with the concepts of DI, the official user documentation and this post by Yassine Benabbas may be better starting points.

Why this particular combination ?

All four of these libraries, provide low-level control to the developer and don't impose superfluous high level abstractions or indirections that get in the way of performance or debuggability.

So we end up with code that is easy to reason through, does not require us to jump through unnecessary indirections and does not require future maintainers to familiarize themselves with an onerous set of conventions before they can get productive with the stack.

About Kotlin

Lastly, Kotlin is a language from JetBrains that targets (among other platforms) the JVM and has excellent interop with Java. So even though all of the above libraries are written in java, we will be able to use them in Kotlin without any additional wrappers and take advantage of the great tooling support by JetBrains.

I am increasingly leaning towards towards Kotlin for most of my backend work because I find kotlin code to be a great blend of conciseness, readability and type safety. Also, being a part of the wider JVM ecosystem, kotlin lends itself well to scalable performant applications.

Getting our feet wet

The official site recommends using https://start.vertx.io/ as a means to bootstrap our application. This site provides a web interface to generate the scaffold for a java or kotlin application using Vert.X.

However, this generator doesn't completely address the needs for our chosen stack, because while we intend to write application code in Kotlin, the vertx-jooq project generates java code for interfacing with the database and hence we'd want our project to be able to compile both java and kotlin.

Following pom.xml, modelled after the kotlin official recommendations for mixed maven projects, provides us all we need to have a hybrid java & kotlin project:

<?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>me.lorefnon</groupId>
<artifactId>test</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
18 lines collapsed (properties)
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Vert.X related dependencies -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-graphql</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-pg-client</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-lang-kotlin</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-redis-client</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<!-- Jooq related dependencies -->
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq</artifactId>
<version>3.13.3</version>
</dependency>
<!-- Library that provides Vert.X compatible code generation for jooq -->
<dependency>
<groupId>io.github.jklingsporn</groupId>
<artifactId>vertx-jooq-classic-reactive</artifactId>
<version>6.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Configuration for building hybrid java+kotlin project -->
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
23 lines collapsed (Execution tags)
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<executions>
21 lines collapsed (Execution tags)
</executions>
</plugin>
<!-- Configuration for running jooq-codegen-maven as part of build lifecycle to generate the DAO classes for type-safe DB access -->
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>3.13.3</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<!-- Our generator needs to connect to the database for inspecting the schema, so we need the postgres jdbc dependencies
even though our application itself will use Vert.x reactive postgres driver instead of jdbc -->
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.2</version>
</dependency>
<dependency>
<groupId>io.github.jklingsporn</groupId>
<artifactId>vertx-jooq-generate</artifactId>
<version>6.1.1</version>
</dependency>
</dependencies>
<!-- Jooq generator configuration: format is the same as for the standalone code generator -->
<configuration>
<!-- JDBC connection parameters -->
<jdbc>
<driver>org.postgresql.Driver</driver>
<url>jdbc:postgresql://localhost:5432/test_development</url>
<user>lorefnon</user>
<!--<password/> -->
</jdbc>
<!-- Generator parameters -->
<generator>
<name>io.github.jklingsporn.vertx.jooq.generate.classic.ClassicReactiveVertxGenerator</name>
<database>
<name>org.jooq.meta.postgres.PostgresDatabase</name>
<includes>.*</includes>
<inputSchema>public</inputSchema>
<outputSchema>public</outputSchema>
<unsignedTypes>false</unsignedTypes>
</database>
<target>
<packageName>me.lorefnon.test.generated</packageName>
<directory>src/main/java</directory>
</target>
<generate>
<interfaces>true</interfaces>
<daos>true</daos>
<fluentSetters>true</fluentSetters>
</generate>
<strategy>
<name>io.github.jklingsporn.vertx.jooq.generate.VertxGeneratorStrategy</name>
</strategy>
</generator>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
24 lines collapsed (Maven Shade Plugin Configuration)
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
<!-- We can use mvn exec:java to run development server -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>${exec-maven-plugin.version}</version>
<configuration>
<!-- Note that the main class is a Vert.X launcher class which will execute the verticles defined in
our application -->
<mainClass>io.vertx.core.Launcher</mainClass>
<arguments>
<argument>run</argument>
<argument>${main.verticle}</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</project>

With this in place, everytime we build our project, the jooq integration will generate the data access layer for our database by inspecting the database.

Our first verticle

The primary unit of deployment in Vert.X is a Verticle. The docs explain this in more detail:

Verticles are chunks of code that get deployed and run by Vert.x. A Vert.x instance maintains N event loop threads (where N by default is core*2) by default

You can think of a verticle as a bit like an actor in the Actor Model.

An application would typically be composed of many verticle instances running in the same Vert.x instance at the same time. The different verticle instances communicate with each other by sending messages on the event bus.

So, this is what our first Verticle looks like:

package tech.lorefnon.test

import io.vertx.core.AbstractVerticle
import io.vertx.core.Promise

class MainVerticle : AbstractVerticle() {

    override fun start(startPromise: Promise<Void>) {
        val router = setupRouter(vertx) // More on this below
        vertx
            .createHttpServer()
            .requestHandler(router)
            .listen(8888) { http ->
                if (http.succeeded()) {
                    startPromise.complete()
                    println("HTTP server started on port 8888")
                } else {
                    startPromise.fail(http.cause());
                }
            }
    }
}

Note that all our setup is explicit in the code and there is no hidden magic going on.

Our setupRouter function will be responsible for bootstrapping the endpoints which handle the GraphQL requests:

import io.vertx.core.Vertx
import io.vertx.ext.web.Router
import io.vertx.ext.web.handler.BodyHandler
import io.vertx.ext.web.handler.graphql.GraphQLHandler

fun setupRouter(vertx: Vertx) =
    Router.router(vertx).also { r ->
        r.route().handler(BodyHandler.create())
        r.post("/graphql").handler(
            GraphQLHandler.create(
                setupGraphQL() // More on this below
            )
        )
    }

BodyHandler is needed to parse POST request body.

Our setupGraphQL function is responsible for wiring up our GraphQL Schema and the resolvers

import graphql.GraphQL
import graphql.execution.*
import graphql.schema.idl.RuntimeWiring.newRuntimeWiring
import graphql.schema.idl.SchemaGenerator
import graphql.schema.idl.SchemaParser
import io.vertx.ext.web.handler.graphql.schema.VertxDataFetcher
fun setupGraphQL() = GraphQL
.newGraphQL(buildExecutableSchema())
.build()
val rawSchema = """
type Query {
hello: String
}
""".trimIndent()
fun buildRuntimeWiring() = newRuntimeWiring()
.type("Query") {
it.dataFetcher("hello") {
// Here is the logic for resolving our hello field in Query type
//
// For now we just return a static string
"world"
}
}
.build()
fun buildExecutableSchema() =
SchemaGenerator().makeExecutableSchema(parseSchema(), buildRuntimeWiring())
fun parseSchema() =
SchemaParser().parse(rawSchema)

Our rawSchema variable contains our very minimal GraphQL schema (defined using GraphQL SDL)

Our buildRuntimeWiring function specifies the resolvers (called dataFetcher in graphql-java terminology) for our dynamic GraphQL fields.

Finally we combine the schema with the runtime wiring (set of resolvers associated with corresponding fields) to form an executable schema which the GraphQLHandler from vertx-graphql package can use to setup the HTTP endpoint which we have mapped to the /graphql route in our setupRouter function.

As we saw in the pom.xml we have an exec:java goal defined. So we can use mvn compile exec:java to run our server.

If we see following message in the server logs, that means we are all good and our server is up and running:

Feb 01, 2021 12:25:02 AM io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer
INFO: Succeeded in deploying verticle
HTTP server started on port 8888

We can try out our API using a GraphQL client. I like using Altair.

Altair

So we see that our simple API works and our field (hello) is resolved through the dataFetcher we have defined.

For now, our minimal resolver returns a simple static string. In the next post we will integrate a basic DI setup using Dagger, and in the in the subsequent one we will setup a signup and signin mechanism where our resolvers will connect to the database for persisting data.