After about eight years of doing back-end engineering, I have often times wondered how to improve the back-end communication potential. We have a few popular mechanisms, such as REST with JSON. In my opinion, REST with JSON is a much better improvement over its popular enterprise sibling, SOAP which inherently includes XML. REST provides much more flexibility, and I truly believe we are only at the beginning of REST based technologies. For example, three years ago, Facebook released a stable version of GraphQL which goes on the belief that a service will cater to multiple consumers, but the consumer gets to decide what they actually want to see. There are probably several use cases where a consumer has several calls to a back-end API and only wants to see what they want to see for that particular scenario.

One scenario I can readily think of is querying on customer information. A customer could have different attributes, including billing information, preferences, and demographic information. There are multiple architecture approaches to this example, a few that come to mind include:

  • Split up attributes to 3 different microservices and have the consumer make 3 calls; one to get billing info, one to get preferences, and one to get demographic information. This scenario can be implemented in several different ways but ultimately would require the 3 separate calls.
  • Have one API that either handles all information (so approaching to a monolith), and then returns all the information. This scenario is not pleasant from consumer or producer…you handle large responses that can get bloated pretty quickly and become hard to read. This also leads EVERY consumer to have to deal with a bit of info they want and the rest just being noise.
  • Have one API that calls 3 different microservices, but then in this scenario you still return all the information in one and then you add to a complex architecture that Engineers may or may not want to touch. The API also has the potential to become slow, because now you are waiting for multiple calls to more back-end services. In my experience, a ridiculous short-cut is to introduce indicators to lessen call volume, but then your API gets bloated with “if” statements.

Personally, I prefer option 1 for simplicity and it hands over control to the consumer…documents are also more streamlined and generally contain less information and are easier to read. Implementation is also easier, and I spend less time with consumers for initial setup. Microservices can get disorganized, though, if you don’t take care of their organization, and in my opinion requires much more orchestration.

So, along comes GraphQL. GraphQL makes the back-end Developers life easier by allowing the consumer to determine what they want to see in the response, and the developer only focuses on determining how to serve up that data. For instance:

In my API, I want to return customer billing, preferences, and demographic information. The consumer lets my API know what they want to see, and GraphQL will determine what to query, by specification from the developer and what it has been told to query. So, theoretically, the API can have multiple DB calls, one for each customer attribute grouping. You, as a Developer, tell GraphQL what DB query (or service call) will match up with each attribute grouping, and GraphQL will only call the DB queries required. You can also replace DB calls with microservice calls, and you can take care of the microservice orchestration yourself.

In my example below, there is a mapping to three tables

  1. billing
  2. preferences
  3. demographic

If the consumer decides they only need billing and preferences, only the billing and preferences table will be called.

How to implement GraphQL

I am using Spring-Boot with Maven. There are multiple implementations for different languages and frameworks. My example branches off of the GraphQL tutorial, so instead of customer billing, preferences and demographics, I will return book, author, and address details. Implementation will be the same, the specifics, like naming will just change.

I define the parent in the pom.

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

As well as the necessary dependencies.

<dependencies>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>11.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-spring-boot-starter-webmvc</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>26.0-jre</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

Next, I add a schema file in my resources directory. You can place this wherever, but you have to let GraphQL know where to pick this up. I will cover this configuration further below.

type Query {
bookById(id: ID): Book
authorById(authorId: ID): Author
getBooks(count: Int): [Book]
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
address: Address
}
type Address {
id: ID
street: String
city: String
state: String
}

Adding to the official GraphQL tutorial, I have a few additional queries, and one additional type. You have to declare the type. The first being “Query”. It has to say “Query” to let GraphQL know these are the queries you are presenting the consumer. The other types can be named whatever you want.

The type Query, has three queries:

  1. bookById — this takes a book id and returns the book. It also will return the “Book” type.
  2. authorById — This takes an author id and returns the author. It will also return the “Author” type.
  3. getBooks — This fetches the first “n” amount of books. It takes an integer as an argument and returns a list of books. Notice when I tell GraphQL to return the “Book” type, I enclose it with square brackets… “[]”…this lets GraphQL know this will be a list.

Fetcher

Please take note, that since this is built with Spring-Boot, I implemented a “@Component” annotation. I implemented 3 lists of maps, one for books, one for author, and one for address. Books contain the id, page count, and author id. The author id is used to fetch the correct author, which has an id, first name, last name, and addressId. The addressId, in turn, maps to the address list of map, which contains the id, street, and state. I truncated the code, so full details is not present here, but can be referenced at my GitHub.

I also create a method that will return DataFetcher, which is a GraphQL class. This will return the relevant data.

@Component
public class GraphQLDataFetchers {
private static List<Map<String, String>> books = Arrays.asList(
ImmutableMap.of("id", "book-1",
"name", "Harry Potter and the Philosopher's Stone",
"pageCount", "223",
"authorId", "author-1"),
ImmutableMap.of("id", "book-2",
"name", "Moby Dick",
"pageCount", "635",
"authorId", "author-2"),
ImmutableMap.of("id", "book-3",
"name", "Interview with the vampire",
"pageCount", "371",
"authorId", "author-3")
);
... public DataFetcher getBookByIdDataFetcher(){
return dataFetchingEnvironment -> {
String bookId = dataFetchingEnvironment.getArgument("id");
return books
.stream()
.filter(book -> book.get("id").equals(bookId))
.findFirst()
.orElse(null);
};
}
public DataFetcher getAuthorDataFetcher(){
return dataFetchingEnvironment -> {
Map<String,String> book = dataFetchingEnvironment.getSource();
String authorId = book.get("authorId");
return authors
.stream()
.filter(author -> author.get("id").equals(authorId))
.findFirst()
.orElse(null);
};
}
...}

If you are familiar with Lambdas and general return functionality, this probably does not look too different. One thing that stood out to me was “dataFetchingEnvironment”. This will return DataFetcher which contains information about the current environment in respect to GraphQL. This will return info from the request, and can also return info for what has already been retrieved.

String bookId = dataFetchingEnvironment.getArgument("id");
...
Map<String,String> book = dataFetchingEnvironment.getSource();

If you recall, “id” is what I declared in the statement of my query:

bookById(id: ID): Book

The “getArgument(“id”) will return this “id”.

How does this all connect?

The last piece to GraphQL is creating the configuration. I created a class called GraphQLProvider.

Notice how I specify in the “init” method to retrieve the “schema.graphqls”. This will load the schema into the factory.

@Component
public class GraphQLProvider {
private GraphQL graphQL;
private GraphQLDataFetchers graphQLDataFetchers;
public GraphQLProvider(GraphQLDataFetchers graphQLDataFetchers){
this.graphQLDataFetchers = graphQLDataFetchers;
}
@Bean
public GraphQL graphQL(){
return graphQL;
}
@PostConstruct
public void init() throws IOException {
URL url = Resources.getResource("schema.graphqls");
String sdl = Resources.toString(url, Charsets.UTF_8);
GraphQLSchema graphQLSchema = buildSchema(sdl);
this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
}
private GraphQLSchema buildSchema(String sdl) {
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
RuntimeWiring runtimeWiring = buildWiring();
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
.type(newTypeWiring("Query")
.dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
.type(newTypeWiring("Query")
.dataFetcher("authorById", graphQLDataFetchers.getAuthor()))
.type(newTypeWiring("Query")
.dataFetcher("getBooks", graphQLDataFetchers.getBooks()))
.type(newTypeWiring("Book")
.dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
.type(newTypeWiring("Author")
.dataFetcher("address", graphQLDataFetchers.getAddressByAuthorId()))
.build();
}
}

A lot of this is boilerplate code to configure GraphQL, but one implementation that I want to call out is the “buildWiring” method.

private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
.type(newTypeWiring("Query")
.dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
.type(newTypeWiring("Query")
.dataFetcher("authorById", graphQLDataFetchers.getAuthor()))
.type(newTypeWiring("Query")
.dataFetcher("getBooks", graphQLDataFetchers.getBooks()))
.type(newTypeWiring("Book")
.dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
.type(newTypeWiring("Author")
.dataFetcher("address", graphQLDataFetchers.getAddressByAuthorId()))
.build();
}

This is pretty cool because here I take those queries and types I declared in the schema and map them to the methods I declare in the GraphQLDataFetchers class. I have to declare the “Query”s here. They will get the “top” level detail. So in the example for getBookById, I declare the wiring as:

newTypeWiring("Query").dataFetcher("bookById",              graphQLDataFetchers.getBookByIdDataFetcher())

See how I mention “bookById”…this has to match the query name in the schema. I also mentioned graphQL will ONLY fetch the top level detail. So GraphQL will get whatever has been declared in Book, but not Author. Author is a second level, and that needs to be declared additionally.

newTypeWiring("Author").dataFetcher("address", graphQLDataFetchers.getAddressByAuthorId())

Here I will tell GraphQL, that when it sees Author, to go to the “getAddressByAuthorId” method.

Final Piece

The final piece is configuring Spring-Boot to run.

@SpringBootApplication
@ComponentScan(basePackages =
{
"com.reinhardt.graphqltutorial.bookdetails",
"com.reinhardt.graphqltutorial.fetchers"
})
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}

All you have to do now is run Spring-Boot. It will by default initialize on port 8080. The URL for GraphQL is “http://…/graphql”

Executing Queries

I used PostMan for this. Two important things to note before you start to test is the URL will be “http://localhost:8080/graphql”. Your hostname and port could obviously differ, but make sure it is followed with “/graphql”. The second thing to note is to change your request to a “POST”. If you use “GET”, you will be required to specify the body as part of the URL.

Within the body, I specify the query name which has to match what I have defined in the schema. I also specify the attributes I have defined including id followed with the id of the book. Next, I specify the attributes to fetch…these have to match the names of the attributes I specified in the schema. I feel it is important to note that when you have a type within a type (author within book), you need to specify the attributes of that internal type as well. Without doing so, GraphQL will throw an error.

I had three queries defined in the schema…they are below:

bookById

This is the request for bookById. I specify the “id” variable with the string I am looking for. I also specify the name, page count, and author. Notice how specify first name and last name of the author as well.
This is the response. It is returned in plain old json.

authorById

Here I specify “authorById” and include the “authorId” variable. I also want to know the address details of the author.
The response is in plain old json.

getBooks

Here I specify “getBooks”. I also have a count…the purpose of this is to show you can specify different attributes. GraphQL can be flexible in how you want to implement it.
I get the list of books back, including the address details.

Final Thoughts

GraphQL is a powerful framework to use, but one that will require some rework from existing structures. I have already started to wonder how several projects I have been part of in the past could have been simplified and if the effort would have been worth it. At the end of the day, as a Back-End Engineer, I have to be focused on delivering value to the consumers of my services. There is no real right answer, and it will totally depend on your use case, but implementation of GraphQL has risen to the top of my list, architecturally speaking.

If you want to see the code or take a swing at it, you can reference my code at my GitHub here -> https://github.com/rvanwyk9191/graphqltutorial

Java API & Microservice Developer for the last 8 years. Currently consulting and tinkering with new technologies.