An idea I've been proliferating recently is that, in the year of our lord 2024, server and client communication should be guaranteed to be wire-compatible. To that end, every greenfield project I've been on has leveraged a framework that produces an OpenAPI specification. You can see the same ideas at play here.
The workflow looks like this:
With OpenAPI tooling you can approach generation from two perspectives:
- Generating both the client and server from a common specification
- Building a server using a framework that exports a specification that can be used to generate clients
There's trade offs to either approach, but I generally use a framework. What is important is that my server and client are wire compatible. That eliminates a lot of end to end and unit tests that I'd have to maintain for the server and every client repository.
The generated clients also, typically, only use the standard library of the language they're generating in. This is useful in minimizing the toil around updating dependencies for each client. Like it or not, vulnerability reporting has become a big part of every engineers week. Minimizing that down to upgrading the language version to fix a vulnerability is quite nice.
Lastly, and maybe most importantly, is that what users can do in my UI they can do in my API because my UI is a subset of my client which is wire-compatible with my API. Integration conversations are often as simple as looking at tests or trying things manually in the UI first.
My remaining toil in software development is largely around coordinated releases. Updating a server and three client repositories in loose order is a task that can take a considerable amount of time. I largely get around this with Make targets that are optimized to generate each client on the host machine, but I long for a way for all of this to be automated away. That's worth a separate post though.