Recently, a new GraphQL javascript client was released called gqless. The idea is really cool and clever: instead of writing GraphQL queries, client write typescript code in their application, and queries are generated at runtime.
gqless is a fundamentally new approach to a GraphQL client. It makes using your API enjoyable, by generating queries at runtime based upon the data your app consumes. (source: https://gqless.dev/docs/intro/what-and-why)
It aims to solve a few client side GraphQL issues, mostly the “double declaration problem”, a problem in which client developers need to first write a GraphQL query string, and then use a similar structure in code at runtime.
In this post I want to go over a few thoughts that popped into my head when I discovered the project last week. This is more food for thought than anything else, and I’d love to discuss it further with anyone!
Declarative, Static Queries
A lot has been written about static queries. One of the idea behind the GraphQL query langage is that it is a declarative language. It’s very easy to look at a query string and know what our clients will receive. This facet of GraphQL is such a big one the the GraphQL client Relay removed advanced query features in the second version of the client.
I used to be really excited about a “query diffing” feature of Relay. If you already had data in a cache, relay would remove those fields from the query at runtime and only execute the subset you were missing. This performance improvement ended up not being worth the “Nondeterminism” of running such an algorithm at runtime. Instead, Relay now takes a build time approach and favors static queries over runtime construction or manipulation of query strings.
This is also why I talk about designing a schema that supports writing static queries in the first place, something I also cover in depth in Production Ready GraphQL
The benefits don’t stop at the client either. Static/Build time queries help out server providers as well. GraphQL operations can have names.
While a name doesn’t require a query to always contain the same set of selections, I find it’s an excellent practice to do so. Monitoring, debugging, analysis and so many other server side concerns are simplified when we stick to this “convention”.
This brings us to persisted queries, one of the most powerful tool in our GraphQL toolkit. Persisted queries allow us to analyze, validate, parse, cache, anything really, ahead of time. At runtime, queries can be ran by using a simple identifier instead of sending the full query string. This is a really powerful way of using GraphQL.
In theory gqless could make these queries work, by registering all possible variations at runtime, but the whole whitelisted / pre-validated approach has to be reconsidered.
For the same reason I don’t particularly enjoy GraphQL query builders:
Abstracting away the GraphQL query language is not a feature to me, and it makes things more unpredictable and also harder to reason about The bottom line here to me is that a predictive & declarative approach to GraphQL is generally one that will avoid pains down the line.
Hiding The Network
The RPC world has battled with this forever. On one end, hiding the complexity of a network call feels great. On the other end, if we hide it too much, we risk mistaking remote calls for local calls (a javascript function call for example).
In Building Microservices, Sam Newman puts this beautifully:
The core idea of RPC is to hide the complexity of a remote call. Many implementations of RPC, though, hide too much. The drive in some forms of RPC to make remote method calls look like local method calls hides the fact that these two things are very different. I can make large numbers of local, in-process calls without worrying overly about the performance. With RPC, though, the cost of marshalling and un-marshalling payloads can be significant, not to mention the time taken to send things over the network. Newman, Sam. Building Microservices: Designing Fine-Grained Systems . O’Reilly Media. Kindle Edition.
I think that remains true with GraphQL. GraphQL performance is honestly a very tricky subject, and being able to see when you’re adding fields to a GraphQL query and how you’re querying things is important. Add debugging to this and I’d rather have the GraphQL query right in front of me.
GraphQL-Powered Liquid Templates
Here’s an interesting story: In 2017, during a Shopify “Hack Days”, a few days where employees can work on an idea for a few days, I had the idea of building GraphQL-powered liquid templates in which users could simply use variables and they would be auto-magically filled in by a GraphQL query behind the scene.
I worked on it with my friend Guillaume Malette. Guillaume quickly warned me and told me we’d never be able to get a fully generic solution to this problem, because of a similar problem to the Halting Problem. We’ll never be able to build something that works 100% of the time with 100% precision. If it is impossible to say with certitude whether or not a program will finish or if a certain branch will be executed, how can we build a static artifact (query) from it?
For an approach like gqless to work generically, we might need to accept certain limitations to the code we write. For example, it might be really hard for a query compiler to know for sure which branches of a program might get executed:
But gqless may accept that it cannot be perfect, and build a query with both branches as an acceptable tradeoff for example. In the end, if we accept to relax our constraints this may work well (most of the time) in practice. But it is additional complexity that needs to be kept in mind.
Alternatives
I risk sounding like a grumpy old man right now, and I don’t necessarily have clear alternatives for those of you fighting with that double declaration problem.
Taking a look at how we can use static and build time tooling to help with this issue might be a promising avenue. For example babel-blade is a middle-of-the-road approach that I really like, and I feel doesn’t suffer the same downsides.
Summary
gqless is an awesome project, and the idea is truly an interesting one. By using an approach like this one though we risk leaving some of GraphQL’s amazing features behind, something we need to keep in mind. I’m looking forward to see how the project will evolve, as I think these things can be balanced and are not necessarily black or white.
If you’ve enjoyed this post, you might enjoy reading a book on GraphQL that I just released last week. I go in details about some of the things we talked about in this post: http://book.productionreadygraphql.com/
Cheers 🍻
- Marc
- Thanks to Guillaume Malette, Nick Schrock & Johannes Schickling for their feedback on this post.*