GraphQL is on the rise as an alternative approach to traditional REST APIs. Created by Facebook, GraphQL provides a better way for the consumer of an API to ask for what they need and get just that. Using a common query language, a client can retrieve data from multiple sources in a single request. Another selling point of GraphQL is the introspection that is available as a result of creating a strongly typed schema. This makes it very easy for the consumer to explore the data and determine which pieces may be of use to them. REST APIs have a variety of ways to make themselves discoverable but with GraphQL you get this for free.
GraphQL provides a better way for the consumer of an API to ask for what they need and get just that.
The benefits of moving to GraphQL are clearly laid out for the client but become foggier for the developer(s) of the GraphQL endpoint. In my experience, creating the endpoint is more straight forward in some languages than others (and GraphQL is implemented in a handful of languages). Zero to GraphQL, by Steven Luscher, does a great job of illustrating the power of this new specification and how it can be used in Python, Ruby, and Node. If you took the time to watch this video you may be feeling a hint of the “SWITCH EVERYTHING TO GRAPHQL” emotion that I was. Like most good demos, he makes it seem like a breeze.
After more research on the topic, I noticed that there weren’t many resources to help with retrofitting GraphQL into a JVM application. Much of my experience lies in this area so I decided to dive in. The rest of this post illustrates how to go about adding a GraphQL endpoint to an existing Groovy Spring Boot app.
For the purpose of this exercise, I stood up a dead simple Spring Boot app. The REST portion of the app has a few endpoints that return data about zookeepers and the animals that they care for. I made a decision to focus only on the query functionality of GraphQL since mutations felt like a topic in their own right. As a result, the app doesn’t support creating or updating resources. A JPA repository layer is used to query the random data that is generated on app startup. This layer will become important when we retrofit GraphQL into the picture.
The schema shown below has two entities with a one to many relationship between them.
Although there are already a ton of awesome projects that might have made life easier, I chose to get a feel for bare bones GraphQL. With the addition of the graphql-java library from Maven to the build.gradle file, I was up and running with access to all things GraphQL.
The general structure of the GraphQL portion of the app is shown below. This was a structure that made sense to me based on the pieces that were needed for the GraphQL endpoint. A few other things were abstracted out to improve testability.
The first layer underneath the GraphQL endpoint is the executor. Overall, this is a pretty boilerplate layer.
The executor’s initial task is to parse the incoming request. It then transforms it into something understood by the GraphQL instance responsible for executing the query against a provided schema. I chose to abstract out the creation of the GraphQL instance into a factory because of the amount of configuration that can go into it. The configuration in my project stayed pretty close to the defaults that were provided in the documentation. The final piece to this layer is to take the query results and package up a response with ‘data’ and ‘errors’. This is what will be returned to the client. The data portion will match the schema that is set up in the following section. The errors will contain, you guessed it, any errors that come up during execution.
The schema in GraphQL does not feel all that different from schemas that get created with other styles of databases. Each schema is typically composed of a set of Object Types (roughly mapped to tables) that contain a set of Fields (roughly mapped to columns).
With the Java implementation of GraphQL, you can either create the schema programmatically or with the interface description language (IDL). Once again looking for the “true GraphQL experience” I created the schema with both methods and configured which method to use during runtime via an app property.
When creating each schema Object Type, you provide a name, an optional description, and a set of fields. The fields feel pretty similar with their own name, optional description, and a data type. With the use of the builder pattern the flow of creating these objects felt pretty smooth.
After piecing together the subtypes, GraphQL requires that you wrap these in a top-level “query” type. This is a special type that defines the entry point for every GraphQL query. This is also a common spot to wire in a data fetcher, which is the glue between GraphQL and the existing data. A data fetcher can wrap the database layer, an API call, or some other data source. The data fetcher receives an Environment object that holds context about the incoming request, arguments, as well as the data being retrieved.
Configuring the schema programmatically gave me a sense of control that I didn’t quite have with the IDL. But given how simple it was to create the same schema using the IDL, I’m not sure how much that would influence my decision in the future. Here is a snippet of the schema created programmatically.
When using the IDL you get to declare the entire schema in a JSON-like structure. This inherently felt very comfortable and intuitive. It is very easy to visualize what the endpoint consumers experience will be since the IDL schema looks exactly like what will be returned from the endpoint. You can also modularize the IDL files to make the schema more maintainable.
Caveat: The IDL allows you to set up the Object Types but you are still responsible for reading in the IDL file and wiring up any data fetchers that are needed. This file shows how I went about creating the runtime wirings.
Here is the same schema defined with the IDL.
GraphiQl is the most common tool for exploring the GraphQL schema. I didn’t get this running and wasn’t keen on burning too much time on it. As a result, I ran all queries via Postman. If this were an API I was serving up to external consumers, I would definitely want GraphiQl up. It provides auto-completion on the schema and a few other tools to help craft GraphQL queries.
Here a few examples of queries that I executed via Postman. Please note that these queries look a little different than those created in GraphiQl, but this is what the query would ultimately be translated to by a tool like GraphiQl.
Once again, all the code for this post can be found on GitHub.