The Hidden Cost of GraphQL Non-Null Fields
Our Federated GraphQL journey at SSENSE started over a year ago when we decided to reimagine the front-end API and introduced a unified data graph, commonly referred to as ‘The Federation’, powered by Apollo’s federation specification.
The federation is meant to be a shared and unified representation of our data, services, and digital capabilities, a common data graph living at the center of value delivery. Teams can now provide data to the graph through their subgraphs and no longer have to worry about out-of-scope data aggregation or duplication.
This paradigm shift, coming from a rich REST microservices architecture, came with a steep learning curve. Developing the federation at SSENSE was a huge learning experience, with lessons learned through research, trial, and error.
In this article, we will discuss one of them: the pitfalls you can expect if you don’t make intentional choices about nullability.
Non-Nullable Fields?
By default, all types are nullable in the GraphQL type system. A nullable type is one that can be represented by null, in the absence of any value. This default behavior is often recommended as a best practice since it forces clients to proactively handle unavailable data.
However, the GraphQL type system allows the usage of non-nullable fields and arguments, denoted by a trailing ‘!’.
At first glance, this seems like a great idea. After all, the type system guarantees that the data will always exist.
Front-end teams can reduce their code size by removing null case checks which, combined with code generation, is a powerful tool to build safer applications when using typed languages.
Nonetheless, there are a few downsides to non-nullable fields worth considering when building a GraphQL API.
Non-Null Fields Make it Difficult to Scale Your Schema
Our applications are in constant evolution, the schema must evolve to suit the business needs. Adding a field in GraphQL is trivial, changing existing fields is not so obvious. Marking a nullable field as non-nullable should not break the clients. Indeed, the clients are aware this field can return null and must decide how to behave when it happens.
However, transforming a non-null field to a nullable one is a breaking change. It will break client code and crash the federation gateway. This is especially important in the context of a federated GraphQL API since a subgraph can reference or extend types defined by another one. Deploying changes becomes a challenge as you have to be careful not to break other subgraphs. In addition, it is increasingly difficult to deprecate fields.
Non-null fields make it much harder to scale the schema. Whenever you’re tempted to make a field non-null, ask yourself if that field is a key or a member of a compound key of an entity (ID, UUID, etc.). Otherwise, make sure your team is ready to live with the downsides.
Small Failures — Monumental Impact
Since non-null type fields cannot return null, whenever an error happens, it is propagated so the parent field can handle it. As per the GraphQL specification, this error will be propagated up to the first nullable field.
Suppose that we have an API with the schema below and our application uses the getProducts query.
The snippet below is an example of the response data under normal operations i.e. without any errors.
Now, let’s imagine an image was not uploaded for a particular product. The response below is an example of how such an error would be handled with nullable fields.
In this example, we have a missing thumbnail, a clear error message explaining the null field, and valuable data. This is effectively a built-in degradation mode giving us the ability to show partial responses to the users of our application.
Let’s change the image type to have only non-null fields and discover what that error would look like.
The error is still the same but has been propagated to the first non-null parent, and the image list has been erased from that product.
Errors, Non-Nullability, and Lists
To understand the full impact of error propagation in GraphQL, we must discuss how nullability works with lists.
In GraphQL, nullability in lists can manifest in one of the following ways:
- A list can be nullable with nullable items
2. A list can be nullable with non-nullable items
3. A list can be non-nullable with nullable items
4. A list can be non-nullable with non-nullable items
If a list type wraps a non-null type, and one of the elements of that list resolves to null, then the entire list must resolve to null. If the list type is also wrapped in a non-null, the field error continues to propagate upwards.
This means that if within our product type the images list is nullable with non-nullable items, images: [Image!], the result is now as below since the error has propagated to the nullable parent.
If however, the images list is non-nullable with non-nullable items, images: [Image!]!, the second product item will now be null because of that error.
This upwards propagation can continue until it wipes out all the available data if our schema only had non-null fields, or if our query was expecting non-null products.
We went from being able to show a partial products list to our users, to showing a blank or an error page.
Instead of handling the error where it occurred, we let it bubble up until no usable data was returned. When using non-nullable fields, we need to be intentional and decide whether we’d rather have partial data or no data at all.
On the flip side, one can argue that GraphQL has poor error handling. Why were the invalid fields not excluded instead of returning null for the whole array of products?
The answer to that question is in the GraphQL type system. Marking a field as non-nullable is synonymous with guaranteeing that even if all that could go wrong went wrong the field would still be resolved.
Conclusion
Our GraphQL and Apollo Federation journey is only beginning. We’ve learned a lot so far and continuously strive to deliver value to our clients.
While the ability to add non-nullable fields to a GraphQL schema is a powerful tool — and can be valuable to front-end teams — it comes with some pretty expensive hidden costs. Ultimately, it’s up to the shareholders to decide if the benefits outweigh the hefty price tag.
Editorial reviews by Catherine Heim & Mario Bittencourt
Want to work with us? Click here to see all open positions at SSENSE!