Introducing Bramble: A Federated GraphQL Gateway Implemented In Go
Today, the Movio engineering team is excited to release Bramble, a federated GraphQL gateway implemented in Go. This new open source project is available for download on GitHub and is released under the MIT license. As a GraphQL API gateway, Bramble supports the following features:
GraphQL federation
Bramble allows you to federate the schemas of multiple services together, i.e. to create a single GraphQL API out of smaller ones. Note that this is different from schema stitching, in that federation allows multiple services to contribute fields to common types. The Apollo team pioneered the concept of GraphQL federation in mid-2019, and Bramble is largely inspired by their work.
Fine-grained authorization system
Bramble supports fine-grained access control which allows you to restrict and hide parts of the schema depending on which client is making a request.
Pluggable architecture
Bramble has been designed to be easily extended via plugins. By default, Bramble contains a number of built-in plugins that offer a wide range of functionality such as a web UI, CORS support, Jaeger tracing support, etc. Writing a Bramble plugin is quite simple, see how to write a plugin, and Bramble's built-in plugins.
Single-binary deployment
Bramble is a single binary and is very easy to deploy in most environments.
Stateless and horizontally-scalable architecture
Bramble is stateless, doesn't require any third-party services, and scales out easily for added reliability and performance.
We hope you find this project useful. If you'd like to try it out, the Bramble documentation has an easy to follow getting started guide. In the rest of this blog post, we'll go through the history of the project, our motivations for creating it, and some comparisons to existing tools.
History of the Project
We started this project because we wanted a better way of sharing data and functionality across multiple teams. We imagine that a lot of other teams have been or are faced with a similar situation: a large legacy database, or a set of legacy APIs that become a bottleneck for new developments, and a desire to build something better.
Here is what our infrastructure looked like after more than a decade of development:
- A primary database used as the source of truth for most data and also used as a communication channel between some services.
- Services exposing APIs using different protocols and encodings (REST, gRPC, JSON, XML, Protobuf).
- Little documentation for internal APIs, and no standardisation between them.
For the sake of brevity, we won't dwell too much on the pain points of the above. In short, maintaining a single database across multiple teams is painful, and maintaining a vast array of internal APIs between all of our teams without a consistent framework for those APIs is also painful.
Legacy infrastructure
When we began ideating the outline of a new architecture, we started by enumerating a number of requirements that were important to us:
Consistent across teams
First, we recognised the need for a standard and uniform way of defining and documenting internal APIs. We were spending way too much time synchronising the development across team boundaries and a unified API platform was priority number one.
Language agnostic
We wanted our API platform to work seamlessly across multiple programming languages and environments. We have teams using Go, Javascript/Typescript, Python, Scala, all for different and valid reasons and we wanted to accommodate each equally well.
Universal
We did not want to have to maintain different kinds of APIs for different kinds of use cases. Ideally, the frontend APIs and backend APIs should use the same technology. We have found in the past that maintaining services that have a public API in say, REST, and a private API in say, gRPC creates a very large overhead. As a result, we choose to have an API technology that is a "least common denominator" in terms of performance but has the benefit of being universal for the whole company.
Human-readable
Finally, we wanted to have an API platform that was easy to evolve and introspect. We wanted to stay away from binary formats and favored technologies that allowed to add functionality easily.
Target infrastructure
Note that the requirements above are tailored to our needs at Movio. Each of them represents a trade-off between ease of use, consistency, and performance. Other organizations may require a different set of trade-offs.
Why GraphQL Federation
During our initial design phase, we quickly narrowed down our choices for an API platform to just two technologies: REST + OpenAPI / Swagger, and GraphQL. In either case, we decided that the best solution would be to have a central API gateway to automatically aggregate all of our services together. Services would expose their API and the gateway would aggregate these services and expose a single unified API. We argued back and forth between those two options for a while, and finally decided to go with GraphQL after reading Apollo's excellent blog post on their new Federation concept for Apollo Server.
For us, GraphQL federation is a real game changer that greatly increases the benefit of using GraphQL, particularly in a microservice environment. The main reason for this is that federation allows for the creation of APIs that appear monolithic, even when implemented by a set of smaller services in the backend. In traditional REST, or when using GraphQL with schema stitching, it is not possible to divide APIs between different services without either making it visible to the API client, or having to write an additional adapter layer in between.
To illustrate how GraphQL federation helps designing great APIs, here's a small example of a Movie API that returns information about movies such as title, director, etc. A traditional REST API for this, would look something like this:
GET /api/v1/movie/583 ⇒
{
"id": "583",
"title": "Iron Man"
}
The equivalent GraphQL API would be very similar:
{ movie(id: "583") { id, title} } ⇒
{
"movie": {
"id": "583",
"title": "Iron Man"
}
}
So far, both REST and GraphQL are not showing any meaningful difference. But what if another team wishes to add the functionality of attaching a poster URL to each movie? One option is to add this functionality to the original Movie service, but what if it were preferable to develop this functionality in a separate service instead?
In REST, without an additional adapter layer, the natural solution is the following:
GET /api/v1/movie/583 ⇒
{
"id": "583",
"title": "Iron Man"
}
GET /api/v1/movie-poster/583 ⇒
{
"movieId": "583",
"posterUrl": "https://..."
}
In GraphQL, using schema stitching will lead to a very similar outcome:
{ movie(id: "583") { id, title} } ⇒
{
"movie": {
"id": "583",
"title": "Iron Man"
}
}
{ moviePosterUrl(movieId: "583") } ⇒
{
"moviePosterUrl": "https://..."
}
Hopefully the example above illustrates the point clearly: in the context of a microservice architecture, APIs that use traditional REST or GraphQL with schema stitching will likely get worse over time due to the proliferation of top-level endpoints / fields. This means that, as you add data fields to your API, it is very hard to not make it also more and more complex over time, and harder for the client to use.
Contrast this to what GraphQL Federation makes possible:
{ movie(id: "583") { id, title, posterUrl } } ⇒
{
"movie": {
"id": "583",
"title": "Iron Man",
"posterUrl": "https://..."
}
}
In the query above, it is completely transparent for the user that title comes from one service and that posterUrl comes from another. The API has the same number of top-level endpoints / fields as before, and is just as easy to use, only richer.
Why Build our Own Gateway?
Once we decided to go with GraphQL federation as our new API framework, we considered the two existing implementations, Apollo Federation and Nautilus Gateway.
Apollo Federation seemed like the obvious choice at first glance, but for us it had two drawbacks. First, we wanted to be comfortable with extending and or modifying the gateway to suit our needs and we have little to no experience with high performance NodeJS backends1. Second, the Apollo Federation syntax is quite complex and we hoped to get away with using something simpler.
Nautilus Gateway looked like a promising alternative to Apollo and is written in Go, which is our bread and butter. In the end, we decided against using it due to it being, at the time, a single developer project with a very short history.
In the end, we decided to build our own implementation, using Nautilus as inspiration. We owe a huge debt of gratitude to Alec Aivazis for his original work. Please note that Bramble ended up being quite a bit more complex than Nautilus, so depending on your use case, Nautilus may be a better fit for your environment.
Bramble at Movio
At the time of writing we have 18 services federated by Bramble, spanning more than 200 types, and all of our newly-developed services are now exposed and accessed through Bramble. In our rollout, we found that having a single entry point for all of our APIs and using a single protocol also presents a lot of advantages in terms of tooling.
In addition to Bramble itself, we developed some internal tools to make our life easier:
- A Kubernetes operator to automatically federate new services
- An internal developer portal to explore and query the graph interactively
- An automatic schema tester for use in CI pipelines
These internal tools have been an absolute boon for the productivity of our engineers. Having consistent tooling across all teams for API management is something we have been wanting to do for a very long time, and we're thrilled it has finally become a reality. The richness and maturity of the GraphQL ecosystem has played a huge role in this. In particular, we recommend checking out GraphQL Playground, GraphQL Code Generator, URQL, and GraphQL Faker, which are all used extensively at Movio.
1 Since then, Apollo Federation has been partially rewritten in Rust but the point still stands.
Internal developer portal
What's Next?
So far, this new approach for developing APIs has been very successful for us and we are already seeing its advantages in the development of new products. This is only the beginning of our GraphQL migration efforts, however. We are still learning a lot and finding new ways to use the gateway. So what's next? We will continue to roll out more and more services internally via Bramble and we will continue to iterate and improve the gateway over time. Bramble has only been recently open sourced, but it has been used in production at Movio for about half a year, so we're quite confident in recommending its use. We hope you find it useful!
We're currently on the lookout for a Golang Software Engineer, find out more about the role here or visit our careers page.