When I wrote “Implement a GraphQL Endpoint in a Groovy App” this past July, I had yet to actually use GraphQL anywhere other than a pet project. Well, that has changed and so have my techniques and opinions on using Spring Boot as a GraphQL server. I’m not going to go over what GraphQL is and why it’s gaining so much support since that information is in my previous post. This post is meant to share a few things I have learned over the past few months using Spring Boot to run GraphQL and why I ultimately transitioned to Node.
I was stubborn at first. I wanted to define the schema and wire up everything myself. This was fine for an example project and also a good way to get to know GraphQL, but I wouldn’t recommend this at scale. There are much better ways to get off the ground running and not worry about the little things. Here are a few tools that I would recommend checking out.
The Java Port of the Node GraphQL Tools library allows you to define the schema using GraphQL’s schema language. This is great, but the real win is how it automatically wires the schema to existing domain objects so that you don’t have to write resolvers for every property in the schema when a simple getter could be used. Another benefit of this library is that it brings in the needed configuration to enable wiring in services from the IOC container in data resolvers/fetchers. Take a peek at the Github page for a glimpse at some of the other great features it offers.
I found this library nice because, with minimal configuration I had a fully functional GraphQL servlet running alongside the regular Spring Boot app. Similar to Spring Boot, an opinionated approach is taken to defining configurations and structuring the project. For those looking to fine-tune the server, a large portion of the configurations can still be manually overridden via bean definitions or application properties. There is also a sibling library to pull in GraphiQL but I’ve recently grown to like GraphQL Playground so I chose to leave it out.
The DataLoader concept is an integral piece of building a performant GraphQL service regardless of if you’re fetching data from a database or another API. Anytime you load a list of items that all need to resolve an item themselves, you run into the N+1 problem. In other words, you load a list of N items (1) and each of those has to then load an item individually (N), hence N+1.
So how does the DataLoader fix this? It serves as a manager layer that can handle caching (in memory or configured to another caching option) but even more importantly it can enable batching requests. The loader debounces requests and once all requests are ready to go, it will make single batch call rather than a series of individual calls. It may seem obvious but the batching support depends on an API or database query that can do a batch lookup.
I’m not going to dive much deeper into this problem as there are plenty of good posts on optimizing queries with DataLoader. Here is a post I found helpful – Optimizing GraphQL Queries with DataLoader.
For a deeper dive of Facebook’s DataLoader concept, check out the docs here. I will reiterate that this is an important concept.
My last blog post explored the idea of sitting a GraphQL endpoint inside an existing Spring Boot app. This may be great for the short term but I started to realize that this felt like an anti-pattern of GraphQL. The point of GraphQL is to pull a variety of data sources together into a single schema (from the consumer’s perspective). By putting the GraphQL server in an existing web service you start to restrict the top-level queries to the domain in that service and muddy up the host service. I realize that even if GraphQL is in one service, it can still call others but it feels like a cleaner architecture to separate it out as its own service rather than muddy up a single service with the extra GraphQL code.
In the months that I have spent exploring the Java implementation of GraphQL, there weren’t many moments where I thought “Wow, that was easy.” There were however many moments where features felt forced and/or lacking. I found myself struggling to find the community and documentation support like I was seeing in other languages. Many libraries had started their journey to Java but just weren’t quite ready for prime time.
The tipping point for me came when trying to pull in the DataLoader to solve the N+1 performance issue mentioned above. The caching portion came easy, although just using an in-memory solution. The batch loading not so much. At the time of writing this, the batch loading simply wasn’t working. There were various workarounds posted on SO and other places but this was the point where I stepped back and asked myself, “Why am I using Spring Boot?” I didn’t have a good enough answer so I took a day or two to prove out the same GraphQL queries in a Node service. As I somewhat expected, this felt like a breeze. I had all of the existing functionality including batch dataloading in a matter of a day. Following that POC, I haven’t look back at Spring Boot as a GraphQL solution and probably won’t unless a client is locked into the JVM.
At the end of the day, you have to assess what technology is going to best suit the problem at hand. For me, I knew GraphQL was a great framework for what I was trying to do. However, I wasn’t sure on the specific language implementation. I was most comfortable with Groovy Spring Boot apps and had already deployed a number of these so I started there. The transition to Node turned out to be a great choice for me and my team, but that not be the case for you. Make sure you evaluate all options.
Here (awesome-graphql) is a nice curated list of all the awesome things going on in the GraphQL community.