Designing APIs with GraphQL + Project Reactor + Caffeine

Back in the days, when I started to dig into Reactive Programming and GraphQL, I realized there was no support yet to integrate both technologies natively, so I came up with a solution that might be useful in certain use cases like the one I am about to describe.

This PoC is all about retrieving lists of airports, countries and cities as if this were an airline. I have found the endpoints to retrieve the info from by easily doing some small research on the Google Chrome Developer Tools.

The whole code of the project can be found here: https://github.com/sergiosheypol/GraphQL_Reactor_BFF

GraphQL can be a nice technology when it comes to implementing Backend for Frontend microservices but Java can be tough sometimes (what a surprise!) with such a level of flexibility. Anyway, I found it interesting because it is very very easy to implement a GraphQL-based API with Spring Boot. As I will be explaining below, it is all about implementing a couple of interfaces and… voilà.

I simply love Project Reactor. I cannot add much more to that. I consider it extremely useful and efficient when in high load systems.

I will not be explaining the basics of GraphQL but how to integrate it with Spring Boot and Project Reactor so I advise you to have a look at other sources before jumping into this one.

Why use a cache?

Here it is the not-that-funny part: GraphQL Java Tools cannot resolve/subscribe to Project Reactor streams. So one fast solution would simply be using Project Reactor subscribe() or block() to retrieve the content of the stream… But here is when the trade-off solution comes in: a cache.

By using a cache, we (kind of) get the best of both worlds. We are not blocking the thread because we are being reactive/async and we have a way to give GraphQL the entities/models it is able to understand. The underlying idea is to check whether the value has already been cached and ask for it asynchronously otherwise.

There is a clear downside in this approach: what happens when the cache has not yet been populated? Well… It is simply going to make the first ever request slower but then everything will work fast.

Project Reactor is not playing any role unless the cache is empty. The most time consuming part in any application is the integration with third parties (databases, microservices, etc) so by making this part reactive, we are improving the performance of that bottleneck.

Libraries

For this PoC, I will be using the following libraries:

pom.xml

Snippet of my pom.xml

Defining queries

schema.graphqls

Defining the allowed queries and mutations must be done inside a schema.graphqls file so let’s start by having a look at it:

schema.graphqls — Defining queries

There are six queries defined. Three of them take a parameter.

As a consequence, we must define our models to match the ones defined in this file… So there must be models called Airport.java, Coordinates.java, Country.java and City.java.

To simplify the example, I am not mapping them throughout the layers of the app. I should have used DTOs, entities and so on but this is simpler and faster.

Another important detail is that ‘Airport’ contains ‘Coordinates’, which is a custom scalar. That is going to force us to write a custom resolver for that model.

Fetching info from sources

provider package

I have used WebClient to connect to remote endpoints to bring the info as it natively returns Mono and Flux items:

Fetching airports from source with WebClient

Configuring in-memory caches

For this PoC, I am using Caffeine cache. Since we are defining three models (airport, city and country), we are using three caches. Here it is one of them:

Caffeine cache instantiation

To endow it with more features, I have wrapped it around a custom class, InMemoryCache, which simply takes a Caffeine cache and performs certain operations on it, like get(…), clear(…)

In-Memory cache wrap

So to create our final cache product (wow!) we just need to instantiate beans of our amazing In-Memory cache as in the example in the bottom of the previous gist.

Business logic. Caching info

In this case, our business logic must handle the cache population and the decision of extracting items from them when it is populated. It needs to use the provider layer to do so.

I decided to create a generic service layer to avoid duplicating code:

Generic service implementation

This generic class looks quite complex but the idea underneath is simple:

  • getAll(): checks whether the cache is not empty and returns it as list. In the case it is empty, it calls the provider layer and populates it.
  • getOne(…): checks whether the cache is not empty and retrieves the value. If the cache is not empty and the value does not exist, it returns an empty object, but in the case that the cache is empty, it simply calls the doGetAll() method to populate it entirely. By doing this, the cache is always consistent and full.

The rest of the methods must be implemented by the child classes. For example, the AirportService class:

Airport service implementation

Exposing our API to the world — Resolver layer

And… last but not least, the resolver. This is the way GraphQL exposes the API. As mentioned above, this is very easy to do in Java by simply implementing the GraphQLQueryResolver:

Airport resolver snippet

This will automatically match the queries defined in your schema.graphqls. Do not worry about the method names… If it is not able to resolve them on its own, the app will simply crash at startup and suggest you the names it is trying to reach.

Coordinates Resolver — Creating our custom resolver

As mentioned above, Coordinates is a custom type, so the app will crash when trying to resolve it. We must define our own resolver. Do not worry about this… If it is needed, the app will simply ask for it. The idea is the same, we must implement an interface (GraphQLResolver<…>) and parametrize it:

Coordinates resolver snippet

The app will work its magic to link it (and will crash if the names are wrong, always suggesting the fix). In this case, the coordinates are in the Airport model, so we need to take it as a parameter.

Running the app

If you are here, it means your app is already set up, so congrats! Jokes aside, this is a normal Spring Boot app, so simply run it.

The default endpoint is /graphql and you can use GraphiQL in /graphiql because we have included it in our pom.xml

Snapshot of GraphiQL

Conclusion

In this guide, we have learned how to create our Spring Boot app with GraphQL and Project Reactor. Despite they are not compatible, we have placed a Caffeine cache to avoid blocking the thread.

Software Developer. Telecommunication Engineer. Music producer. DJ. @codingsheypol

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store