Benjie is the community–funded maintainer of the Graphile suite — a collection of MIT–licensed Node.js developer tooling for GraphQL and/or PostgreSQL. A key project in this suite is PostGraphile, a high performance, customizable and extensible GraphQL schema generator for APIs backed primarily (but not exclusively!) by PostgreSQL.
In this post Benjie tells his story of bringing a new paradigm to PostGraphile to enable it to become more powerful and performant than ever before — this new system is coming in PostGraphile Version 5, available in pre–release now for all Graphile sponsors, find out more at postgraphile.org
Something was fundamentally problematic in the design of PostGraphile Version 4 — the look–ahead system. The system that gave PostGraphile its power and performance was also the system that made it hard to maintain and hard to add new functionality to. Though few ever needed to interact with it directly, it was hard to teach those who did how to use the look–ahead system when even I struggled with it — and I was the one who wrote it! I built abstractions to make the common tasks more straightforward for users, but everything needed its own little helper, and the helpers were inconsistent. Messy. Verbose.
I wanted PostGraphile V5 to support all the new features of GraphQL, but even patching well–established features such as polymorphism into the look–ahead system seemed insurmountable — it wasn’t built with that in mind. This was hardly a surprise, the original system was hacked together in just 2 weeks! I needed a better solution…
It was February 2020. I had returned from the FOSDEM conference in Brussels and was lying sick in bed with a fever. (Immunosuppressants and conferences are not a winning combination.) As I went in and out of fevers, a thought struck me… What if instead of writing code that executes, we wrote code that described the execution. GraphQL is declarative, but resolvers are procedural… What if, instead, we made declarative resolvers?
When I could lift my head without the room spinning, I scribbled some notes in my notebook. I started by doing what I hadn't with look–ahead: focusing on the code the users would write. In fact, that's all I focused on, and just kept telling myself that all the information the system needed was there in that code… I just needed to build a system that could understand and execute it.
That turned out to be a long process with many false starts. Over the 3 years I spent working on this project (on and off, between client work and open source responsibilities, grabbing a few hours here and there to peck away at it) I rewrote the engine 5 times. Each time I ended up with something that worked, mostly, but it would fail at some point near the end due to some slight oversight, each time making me revisit the drawing board.
Throughout these rewrites, the "plan resolver" code that users would write (or PostGraphile would generate for you) barely changed — it was just the engine that needed to evolve. "Plan resolvers" differ from traditional GraphQL resolvers in that instead of calling the code to do the work of fetching the data necessary to fulfil the request (as traditional resolvers do), they serve to build a "field plan" that describes the required actions (or "steps"). The system then combines all of these field plans into an "operation plan", and it can manipulate the steps in this plan — merging, replacing, reorganizing and optimizing — ultimately resulting in a highly optimized plan that minimizes the work that backend services need to do.
I saw this stability of the plan resolvers despite major changes to the planning and execution engine as a validation of the core of my idea — the abstraction was right, I just needed to figure out how to build the final engine.
And, build it, I did! (Eventually…)
One of the final breakthroughs I had was that piggy–backing off of GraphQL.js' execution model just wasn't going to let me do everything I needed the system to do, at least not as quickly as I wanted it to do it!
So, rather than continuing to squeeze a declarative holistic execution system inside of a linear execution system, I decided to write my own GraphQL execution engine. Ultimately, this resulted in Grafast, the next generation planning and execution engine for GraphQL! You can find more about this and what makes it so exciting, not just for PostGraphile but for the entire GraphQL ecosystem, in this video:
Grafast solved all of my issues with look–ahead, and was more powerful and flexible too! It was, and remains to be, a joy to work with. Grafast is better than look–ahead in every way I cared about: plans are simpler to write and maintain, easier to read, they execute faster, they are more capable — handling polymorphism and incremental delivery and all these other fun things that GraphQL gives us access to — and the SQL queries that the system built for V5 were so much simpler and easier to read (and faster to execute!) than those in V4.
There was a problem.
A big problem.
PostGraphile V4 was entirely built on top of the look–ahead engine. Every type, every field, every argument interacts with the look–ahead engine. And I'd relegated the look–ahead engine to the trash! With the look–ahead system removed, almost nothing worked.
So I set about doing what we're always told not to do: I rebuilt everything. From scratch.
And, since I was rebuilding it all anyway, I could solve a few of the other problems that had been bugging me… 🤔
Check back next week forNext, check out Part 2: Plugins and Presets; and remember: to get your hands on a pre–release version of PostGraphile V5, all you need to do is sponsor Graphile at any tier and then drop us an email or DM! Find out more at graphile.org/sponsor