How we learned to stop worrying and love GraphQL

Andrey Los
Revolut Tech
Published in
8 min readAug 8, 2019

--

This is the first part of the story of our GraphQL experiment at Revolut.

This part will explain:

  • Why we’ve started using GraphQL
  • How we’ve hacked the proof of concept phase of the adoption
  • How you can do the same in your project

I’ve been playing with GraphQL since early 2017 before I joined Revolut. Then, in December 2017 I joined Revolut and I wasn’t in the position of offering a new stack. But a year later, when our team grew to 3 times its size and I got more confidence, use cases for GraphQL started to appear one after another. At that point, I decided it’s time to start taking GraphQL seriously into consideration.

Issues prior to GraphQL implementation

Here are some of the issues I wanted to tackle using GraphQL:

TypeScript and stale API typings

Around June 2018 we’ve decided that we need to add static types into our codebase. At that point, TypeScript got enough traction and made the choice of using it over Flow a safer bet.

We started with a bunch of d.ts files here and there to eventually all-in into TypeScript.

Very quickly we realized that the most valuable area for types but also the hardest one to keep up to date was our APIs. APIs always tend to have limited or no documentation, it keeps changing and it’s the most important because it is based on code that is out of our reach.

Also, even when we managed to maintain those types, they don’t have any runtime validations, so they are getting out of sync very quickly and we had to add more code to actually do runtime validations.

More REST, more pain

After a while, the number of endpoints at Revolut started to grow exponentially. It made it harder to keep track of what we can fetch. We also needed to call multiple endpoints with lots of data and then manage all that data in Redux. This also included managing and invalidating the cache.

Redux burden

To reduce the repetition and improve the developer experience, one of our lovely engineers (thanks to Pavel Maryanov) created a simple, yet quite a convenient helper that reduced our pain a bit. Being nice and concise at the beginning, after a few months it got very complex because of the new requirements. That of course came with maintainability issues related to code changes, testing, teaching, etc. Not to mention there’s still a lot of boilerplate to deal with.

So, eventually, I sat down and started thinking.

Should we maybe try this fancy GraphQL with Apollo thing? It worked perfectly for me before.

The next day I started seeking where I could try it out softly, on a simple feature, so nobody would know/will be affected except for me and my reviewers. I knew it should have been a team effort, but everyone was too busy with their stuff… and also the thought of having yet another meeting?! I would rather code something quick and real to prove my point!

Talk is cheap. Show me the code. © Linus Torvalds

Proof of concept

I’ve tried to figure out how to do it, so it will require:

  • A minimum amount of time and effort,
  • No infrastructure changes.

While I was thinking about it, I got to meet some nice folks from The Guild (authors of GraphQL Codegen and many other GraphQL Tools).

They were happy to chat and helped me realize one small but crucial thing:

Coming to GraphQL and reading a lot of articles and guides, I’ve been seeing the same thing over and over again: “Just™️ create a GraphQL proxy server over your RPC/REST”.

My response was:

“JUST” seems to be a whole new piece of server that you need to integrate into the system with logging, load balancing, error reportings, CI/CD, repository and many other things. It’s long and expensive!

So how the hell do I do the PoC? The Guild folks suggested the following idea:

What if you run your GraphQL proxy server ON THE CLIENT?

Me:

WAT!?

They continued:

GraphQL is just a function! It takes a query and executes resolver functions according to the GraphQL specification. Nothing in there is saying anything about the network, the servers or the clients! If you want to communicate using piegons, go for it! Just be sure it’s a JSON :D

So, GraphQL on Client? While it may sound easy, to write a spec compatible runtime on the client is not a trivial task. But fortunately, there is a solution.

Apollo Client (more about it later) has a concept named links. It’s, basically, middleware API that gives developers an ability to intervene in its internal flows changing them, reading them, etc. that gives developers options to do error logging, reporting, analytics, you name it.

The main point is that there is an apollo-link-schema that was designed for the unit testing (mocking of the server) and server-side rendering when SSR happens on the same server that does GraphQL. In other words, it’s exactly “GraphQL Query runtime engine” that we need in the form of the NPM-package.

It means that we can effectively try to run that on client-side praying that it will just work in the web environment. And you know what? It works! Here is how you do it.

The only thing that you need to understand here is that it’s effectively something around 100–150kbs gzipped runtime that will end up in your client’s bundle, which is a bad idea for something that is not an internal tool. But in our case, and many others, it was a perfect match for our needs and the best entry point into the GraphQL world!

So I grabbed one simple feature and implemented it using this approach and it worked out flawlessly! After some time and more features and improvements, I presented it to the team.

People who were familiar with GraphQL before liked it immediately, some were OK, and few were tentative. In the end, we decided to give it a try where it made the most sense.

Playing with new tech is cool, but you better stay pragmatic. So how exactly GraphQL helps us solve the issues that I mentioned above?

What GraphQL solves and gives us

TypeScript and stale API typings — fixed

Any GraphQL implementation is checking input parameters and what resolvers are resolving (e.g. what will be returned to the client), on correctness against the defined static schema.

For example, if your field fullName on type User is non-nullable, but your resolver returns null, query execution will fail with an error.

It means, that by implementing GraphQL over our REST/RPC endpoints we will be able to track down our APIs at least for unexpected breaking changes and immediately react to them before it goes into production. Sounds awesome!

Do you know what is also awesome? That GraphQL schema types and TypeScript types that you will want to use in your clients will always be in sync with no additional work to do. How? Thanks to the awesome graphql-codegen that will do all the heavy lifting for you with one config file and one function call.

More REST, more pain — fixed

It doesn’t cure REST problems that REST has by design. But it reduces it nicely if implemented on a real proxy server. GraphQL Proxy sits closer to the metal (e.g. other backend services) and network latency at that level should be around 2–4ms for an HTTP round trip, so most of the query resolving time actually takes data processing and DB querying, while web client pays a latency price only once, not for each request like they used to do before with REST. Roundtrips and latency were not an issue for us in particular, but it’s nice to mention anyway.

What was the biggest issue, is multiple calls orchestration. It’s a lot of boilerplate you need to write to get all of the resources for the client. It’s annoying, time-consuming and really hard to abstract it into something simple enough so it’s just a one function call or something close to that experience.

All that contributes to Redux burden, that keeps growing.

Redux burden — reduced

One of the things Redux is frequently used for is the handling of your client data layer. You fetch a lot of resources and you put them into the global store of Redux that then you consume in many places in your app. In our case, 99% of our Redux store state is just data that got fetched from the backend.

Global UI state in Redux for us is just notification system and modals that simply replaceable by new React Context API and 2–3 custom hooks.

How GraphQL and Redux are even interconnected you ask? The cool thing about GraphQL that it’s a spec that constrains a lot of things, but it also means it’s predictable. Hence, you can abstract away a lot of boilerplate once and forever by writing a generic GraphQL client. And Apollo Client is exactly that thing.

Apollo Client works with React and many other frameworks to simplify data fetching, caching, optimistic responses, cache updates, cache invalidation, error handling, and other stuff to just 3 hooks to serve it all.

To make it up and running you will need one small createConfig.js file, that is sized like typical Redux createStore.js, and basically, an <ApolloProvider client={client} />over your root <App/> component and that is it.

So effectively by migration from Redux to Apollo for the data layer you can remove a LOT of code from your codebase. Less code, fewer things to support, fewer bugs and better productivity. Not to mention free docs and a lot of free stuff online already made for you, because it’s an open-source babe!

Other stuff

  • Fully typed boilerplate generation out of queries/mutations that you’re writing when requesting the data using GraphQL Code Generator with @graphql-codegen/typescript-apollo preset
  • Enforced documentation. If we cover something up with GraphQL, it’s there, visible, searchable in one place in GraphiQL IDE, play with it here if you don’t know what it is
  • REST call orchestration done right in one unified way on the server for all use-cases possible, not for a single one
  • Fields utilization distribution metrics by each client using Apollo Graph Manager giving us the ability to deprecate unused fields with ease
  • API breaking changes detection on CI checks when GraphQL Schema changes using GraphQL Inspector
  • Client business logic located in one isolated place, the GraphQL Proxy, to reduce data massaging logic from the clients
  • Many others that will come, because GraphQL is very powerful and community is awesome

Conclusion

GraphQL at Revolut is still far from perfect and on the early phase, we still have some issues that we need to fix and improve, learning is hard, especially in our extremely fast-paced environment.

But for us, the benefits outweigh the downsides by far!

While GraphQL is not suitable for everything (e.g. high-performance, low latency microservices communication) it’s definitely amazing and way more powerful than REST for any public (with few challenges depending on use case) and internal APIs for client apps (Web, Mobile).

On an advanced level, it requires some learning curve and practice, but it pays off in productivity and quality significantly, even if done somewhat good, not to mention the perfect implementations.

That is it for today. Hope you have enjoyed the story!

Next part will present more code examples, timelines, problems that we encountered and many other things.

P.S: Talking about perfect schemes though, highly recommend a community-driven set of rules website on how you should design GraphQL Schemas so everyone will be happy.

Join Revolut

--

--

Andrey Los
Revolut Tech

Senior Software Engineer — Revolut. In ❤️ with: singing (music), coding, games, self-improvement and Formula 1. github.com/RIP21