Imperative, Declarative, and Workflow APIs
16 Jul 2019
At every turn in my API work I come across folks who claim that declarative APIs solutions are superior to imperative ones. They want comprehensive, single implementation, do it all their way approaches, over granular, multiple implementation API calls that are well defined by the platform. Declarative calls allow you to define a single JSON or YAML declaration that can then be consumed to accomplish many things, abstracting away the complexity of doing those many things, and just getting it done. Imperative API interfaces require many individual API calls to tweak each and every knob or dial on the system, but is something that is often viewed as more cumbersome from a seasoned integrator, but for newer, and lower level integrators a well designed imperative API can be an important lifeline.
Declarative APIs are almost always positioned against imperative APIs. Savvier, more experienced developers almost always want declarations. Where newer developers and those without a full view of the landscape, often prefer well designed imperative APIs that do one thing well. From my experience, I always try to frame the debate as imperative and declarative where the most vocal developers on the subject prefer to frame it as declarative vs imperative. I regularly have seasoned API developers “declare” that I am crazy for defining every knob and dial of an API resource, without any regard for use cases beyond what they see. They know the landscape, don’t want to be burdened them with having to pull every knob and dial, just give them one interface to declare everything they desire. A single endpoint with massive JSON or YAML post or abstract it all away with an SDK, Ansible, GraphQL, or a Terraform solution. Demanding that a platform meet their needs, without ever considering how more advanced solutions are delivered and executed, or the lower level folks who are on boarding with a platform, and may not have the same view of what is required to operate at that scale.
I am all for providing declarative API solutions for advanced users, however, not at the expense of the new developers, or the lower level ones who spend their day wiring together each individual knob or dial, so it can be used individually, or abstracted away as part of more declarative solution. I find myself regularly being the voice for these people, even though I myself, prefer a declarative solution. I see the need for both imperative and declarative, and understand the importance of good imperative API design to drive and orchestrate quality declarative API implementations, and even more flexible workflow solutions, which in my experience are often what processes, breaks down, and executes most declarations that are fed into a system. The challenge in these discussions are that the people in the know, who want the declarative solutions, are always the loudest and usually higher up on the food chain when it comes to getting things done. They can usually rock the boat, command the room, and dictate how things will get delivered, rarely ever taking into consideration the full scope of the landscape, especially what lower level people encounter in their daily work.
For me, the quality of an API always starts with the imperative design, and how well you think through the developer experience around every dial and knob, which will ultimately work to serve (or not), a more declarative and workflow approach. They all work together. If you dismiss the imperative, and bury it within an SDK, Ansible, or Terraform solution, you will end up with an inferior end solution. They all have to work in concert, and at some point you will have to be down in the weeds turning knobs and dials, understanding what is possible to orchestrate the overall solution we want. It is all about ensuring there is observability and awareness in how our API solutions work, while providing very granular approaches to putting them to work, while also ensuring there are simple, useable comprehensive approaches to moving mountains with hundreds or thousands of APIs. To do this properly, you can’t be dismissing the importance of imperative API design, in the service of your declarative—-if you do this, you will cannibalize the developer experience at the lower levels, and eventually your declarations will become incomplete, clunky, and do not deliver the correct “big picture” vision you will need of the landscape.
When talking API strategy with folks I can always tell how isolated someone is based upon whether they see it as declarative vs imperative, or declarative and imperative. If it is the former, they aren’t very concerned with with others needs. Something that will undoubtedly introduce friction as the API solutions being delivered, because they aren’t concerned with the finer details of API design, or the perspectives of the more junior level, or newer developers to the conversation. They see these workers in the same way they see imperative APIs, something that should be abstracted away, and is of no concern for them. Just make it work. Something that will get us to the same place our earlier command and control, waterfall, monolith software development practices have left us. With massive, immovable, monolith solutions that are comprehensive and known by a few, but ultimately cannot pivot, change, evolve, or be used in new ways because you have declared one or two ways in with the platform should be used. Rather than making things more modular, flexible, and orchestrate-able, where anyone can craft (and declare) a new way of stitching things together, painting an entirely new picture of the landscape with the same knobs and dials used before, but done so in a different way than was ever conceived by previous API architects.