April 05, 2020
Following is an internal blog post from 2019 that I wrote up for my team at Target to convince the other engineers that we should adopt GraphQL. We were in the very early stages of a re-platforming of our internal CMS and were deciding on technologies to use. We decided to move forward with GraphQL which was 100% the right decision given our application’s needs
I removed references to our internal CMS’s name and dependent services, hence the generic use of “CMS” and “Service X”, “Service Y”, etc
Over the past couple of weeks, I’ve been playing around with GraphQL, a specification for fetching data that is an alternative to REST. In this post, I’d like to do a light comparison between GraphQL and REST, describe issues that we face on the client in regards to data fetching and provide examples of a functioning application that’s built around the CMS domain in an effort to display the benefits that GraphQL can afford us.
In short, GraphQL takes a “client-first” approach to data fetching where the client specifies exactly what data it needs via a GraphQL query (think SQL), sends this query to a GraphQL server and the server returns exactly that data back to the client.
Compare this to REST, which is more “server focused”. A service that exposes endpoints that follow REST principals are rigid and generally follow a pattern of:
Entities that have relationships with other entities (either via collections or a single property) might return those child entities fully hydrated in the GET call or they might just return a pointer (either a url to get that entity or an id) to the child entity. A GET endpoint for a single entity will generally return all fields for that entity.
These characteristics of REST present a few problems from the client’s perspective:
The above friction points from the client perspective could be somewhat alleviated by introducing a service that exposes custom, non REST endpoints that are specific to the client’s needs, however:
These issues are greatly exacerbated as more and more services are required by the client to function. It’s worth noting that the current CMS UI suffers from each problem noted above.
GraphQL alleviates much of the friction with REST outlined above as well as offers additional features that help improve developer experience:
It’s important to note that GraphQL is a specification, not a specific piece of software, per se. As such, there are implementations of GraphQL servers in all popular server stacks (JVM, .NET, NodeJS). To integrate GraphQL in your platform, you’ll need:
GraphQL server - there are numerous implementations of GraphQL servers. This GraphQL server can sit on top of existing REST services or have direct access to your data stores. In the app referenced below, I’ve chosen Apollo due to their top notch documentation and the impression that they’re the current leaders in GraphQL implementations.
GraphQL client - depending on your view library (e.g. React), there are various options for a GraphQL client. I’ve chosen Apollo Client (specifically apollo-boost) which integrates with React quite well.
That’s it! GraphQL provides for authentication, tracing and all other functionality you’d expect from a service.
I believe that GraphQL will greatly simplify our client code and unlock some productivity boosts when implementing new features. Most of the friction I’ve experienced when introducing new features is related to wiring up data on the client. This work requires a lot of boilerplate related to redux:
Due to the above points, much of the code in the CMS UI is made up of logic related purely to data fetching and orchestration:
All Code | LOC | % |
---|---|---|
Total Lines of Code | 23,900 | 100 |
Data Fetching Related | 8,463 | 35.4 |
Over a third of our codebase is related to fetching data for the application. Digging into those LOC related to data fetching:
Data Fetching Related Code | LOC |
---|---|
Test Code | 5,214 |
Redux | 5,024 |
Redux Test Code | 3,192 |
As mentioned above, there is some complexity inherent to data fetching and orchestration and this has resulted in a bunch of test writing to ensure this behavior is correct. We can see this in the number of LOC in test files related to data fetching. Similarly, Redux is quite verbose and can get complicated quickly. We can see this in the LOC as Redux code comprises over 20% of our codebase, with over 60% of that code being test files.
GraphQL would eliminate the need for nearly all of this code as data fetching logic would be moved from the client to a GraphQL server.
It’s important to note that Redux will likely not be needed if we migrate to GraphQL.
To play around with GraphQL on the server and client, I added a feature to our admin app that allows the user to search for a page in publishing service and view information (slot information, component status, slot overrides) for a specific page slice. This application consists of a server and client:
graphql-server - GraphQL server that the admin-app’s “page” feature depends on.
admin-app - I’ve created a branch that includes a “Page”…page that contains the features mentioned above.
This example puts the features inherent to GraphQL on full display. In short, the Page view shows data that spans multiple services and resources:
While this data is ultimately utilized by the client, the client has no idea which system the data came from, nor does it care. It simply issues a query to the GraphQL server detailing all the fields it needs. The client has virtually zero logic related to fetching data, aside from code that describes the GraphQL query that is sent to the server. The server handles this query and performs the aggregation and orchestration in a reusable and elegant fashion.
It is worth mentioning that, despite the fact this feature is not 100% fleshed out, it does include quite a bit of data and closely mirrors the current CMS UI.
And it achieves this with much, much less code:
-the client feature is composed of 9-10 files, mostly React components -the service is composed of 4 files -neither the client nor the server contain tests . Though looking at the code, testing these features both on client and server should be trivial.
A big takeaway from the above information is that a relatively rich application feature can be achieved with a small amount of code on the frontend. And while we do need both a client and server to achieve this feature, there is a better separation of concerns in the application stack where the client can focus purely on UI rendering and the server focuses on responding to a request and fetching/aggregating only the data the client needs based on said request.
This is me, today, commenting on the decision to adopt GraphQL in our platform. Following was not in the original post to my team
The adoption of GraphQL in our platform rewrite greatly improved the developer experience in the UI as well as allowed a healthy separation between frontend and backend in regards to data fetching. It also promoted an effective approach to new features where UI engineers and backend engineers would discuss schema design and, upon agreement, would go to their respective codebases and develop against this contract.
Adoption of GraphQL was not without its pitfalls, false starts and refactorings; all of which I hope to capture in a future post. However, the benefits far exceeded the drawbacks and allowed us to deliver a ton of useful features in less time than if we continued down the Redux path we were previously on.