Disclaimer: this post wasn't planned at all, but the concept kept bothering me, so I couldn't resist turning it (spontaneously) into a blog post ...


Let's consider Web APIs, for a minute. For a reason, as they are still the foundation of the Internet as we know it. The typical way of building an API is to use a web framework (Django, Spring Boot, Express, ...) that is conceptually based (some more tightly, some not really) on one of two popular models: MVC or MVVM (that's why they tend to use very similar vocabulary, regardless of the programming language used). Such APIs are generally advised to be stateless, as the persisted data is kept in (various) DBs. Easy stuff.

For quite a long time, such APIs were deployed in a very straightforward way - the whole app was running on a single web server process (external or built-in), behind a pool of web threads, with a shared routing schema that was re-directing requests to particular controller methods. That model has changed slightly after the introduction of the serverless paradigm: each controller method (URL + method) gets a Lambda function handler. This means that to turn a "traditional" Web API into a serverless one, one has to shred the project into tiny slices, bundle (manually) shared dependencies, integrate the functions with API Gateway, etc.

My question is:

WHY?!

The longer I think about it, the more I'm convinced it should look like this:

  • the level of abstraction used when you design and implement API (in simple words: turn business logic into code) should not cover any aspects of deployment - it has to be universal (and TBH: in the vast majority of cases, it already is)
  • the same codebase (of the Web API) should be deployable to bare metal instances, containers, Lambda functions, or whatnot - the only change needed (when moving from one deployment option to another) should be in the deployment (configuration & mechanism)
  • deployment mechanism should be able to determine (automatically, but customizable with hints), e.g., whether to split the code or not (into separate deployment packages), what dependencies to include in packages (based on direct/indirect references in code), where to deploy routing configuration (web server, API Gateway, somewhere else), etc.

Such an approach may have been impractical a few years ago when web frameworks were relatively heavy and bloated with excessive dependencies. But we're living in times of Flask, Micronaut, or Actix - either compiled to tiny binaries or running on wildly optimized VMs like GraalVM. And the benefits are clear: more interoperability, more flexibility (when it comes to picking correct compute cloud services), and better separation of concerns (infrastructural & logical/business ones).

Yes, it could require adding a few more constraints for the framework to enforce (e.g., 100% statelessness, adding some more abstractions, generalizing cross-cutting concerns - so they don't require customizations in some deployment options) - but it definitely seems doable.

To conclude:

  1. do you know any web framework that indeed DOES (already) fully separate the deployment specifics from the API contract & business logic? (SAME code deployable in several different compute services)
  2. or are there any conceptual/technical obstacles that would prevent implementing such an approach (& I've simply omitted them due to my ignorance)?

Anyway, if none of the answers is YES, I'm definitely going to experiment with something like that (which will probably require building some code-generation layer on top of an existing framework). Preferably on some solid foundation, like this project: https://github.com/micronaut-projects/micronaut-aws.

Share this post