Today I want to write about a nice little utility for communicating between GraphQL microservices.
At Workpop, our architecture looks like this:
Johannes Schickling did a great job on this diagram because it really illustrates our service setup perfectly.
Our frontend clients communicate with a GraphQL Gateway: a service that merges all the schemas of our GraphQL microservices and forwards incoming requests to the correct underlying service that owns that API.
This makes requests coming in through the front door of the application stack easy because you only have to worry about one endpoint and can query the whole system!
This article is going to dive into how data moves within our cluster of services.
Workpop runs microservices using Kubernetes and hosted via the Google Cloud Platform. 90% of our services are “GraphQL native”, meaning they were built from the ground up to have GraphQL as the API technology.
Initially with the GraphQL gateway in mind, we blazed a path for the front end to really leverage GraphQL and for us to iterate/monitor/deploy quickly on our backend APIs. This was working great, we had services controlling the domains they needed to for our business/product requirements.
As most engineers know, in a startup your requirements are constantly in motion. So when we started having use cases where certain services needed some information from another service we had to think of a way to access this data without having to refactor a lot of our GraphQL native code.
Enter GraphQL Bindings
graphql-binding - Auto-generated SDK for your GraphQL API (supports schema stitching & codegen)
GraphQL Bindings are modular building blocks that allow you to embed the APIs of a GraphQL server anywhere. With these bindings you can leverage the data exposed from your GraphQL server in many situations, here are a few use cases:
- Let’s say your application architecture has an event queue. While services are pulling events off the queue, they can use the APIs from bindings to process the correct mutations that should happen.
- Let’s say you’re listening to webhooks from Stripe. Your service can read the incoming metadata, use bindings to fetch and format some data and write it to your database via mutations.
- Let’s say you’re migrating pieces of your data access to GraphQL. Bindings can help you replace calls inside your REST API with GraphQL APIs . This allows you to incrementally refactor as you adopt more GraphQL into your stack.
More info here:
Reusing & Composing GraphQL APIs with GraphQL Bindings | Prisma
In previous blog posts, we introduced the idea of schema stitching as an elegant way of connecting multiple GraphQL…
GraphQL bindings confused my team at first. The way that helped them solidify the concept was comparing a GraphQL binding to a 3rd party SDK, like Twilio. You instantiate it with some credentials and now you have access to the API of that sdk:
So for example, let’s say your GraphQL Schema looked like this:
Translated to what a GraphQL Binding essentially does under the hood:
And you could use this in a function like this:
Now that we know what bindings are, this is how we implemented bindings for our service to service communication.
First we made something called a “Service Binding”
Let me break down what this does:
First, we extend the GraphQL binding class to instantiate our schema.
Next, within the constructor we make a HttpLink which represents the “transport” layer of the binding.
In this case, when binding apis are called they’ll be making HTTP requests to the URI of the service.
The magic sauce here comes from
makeRemoteExecutableSchema. With this, resolvers are executed from the URI we configured.
Kubernetes to the rescue
Once we made this Service Binding, my mind immediately drifted to integrating this binding operationally across different environments. Prior to using Google Cloud, we were on Amazon ECS and we had separate URLs for our services in different environments e.g. staging, develop, prod.
A really cool feature in Kubernetes is something called “Internal DNS”. In Kubernetes every service defined in your cluster is assigned a DNS name. Kubernetes also runs a DNS server itself which has the a list of running services and their service names (configured by the user).
Let’s say we have a microservice named Jobs in the staging Kubernetes namespace. Any other service running in this namespace can look up Jobs by doing a DNS lookup for Jobs.
What that means for us is we can instantiate a service binding like this:
So assuming we have a service named Jobs in our Kubernetes cluster running on port 1337, any request to this URI will hit the Kubernetes DNS table and then forward the request to that service.
Putting in context
Now that we have an understanding of GraphQL Bindings and our Service Binding, let’s put this in context.
Here we have another service that handles “Applications”. You can see we attach our bindings to the GraphQL context object so any resolver could potentially leverage the jobs binding when resolving data.
In this article we explained how we leveraged GraphQL bindings to provide LEGO blocks for service to service communication! We also explore how Kubernetes DNS helps the operational use of a binding within your cluster of microservices. Now that we have a bunch of bindings, we have AUTOGENERATED our GraphQL gateway (more on that in another post!)
GraphQL bindings are a fantastic tool to easily reuse, share and compose existing GraphQL APIs, get started with them today!