Quick Intro

The Serverless Trilemma is a framework developed by researchers in IBM a couple of years ago (full academic paper). It helps us evaluate the quality of architectural designs and make better decisions by understanding the trade-offs involved.

This is an introductory article, we'll publish more hands-on guidance on how to apply these principles in next articles on this series. To stay tuned, please subscribe here.

Basic Constraints

There are three desired properties we should look for in a serverless architecture:

  1. Black-box
  2. Double-billing
  3. Substitution

Black-box

The Black Box principle indicates that each function should work independently from the rest of the system and no implementation details should be leaked.

Consider two functions running on AWS Lambda: A and B .

Function A uses the AWS SDK to invoke Function B .

Black-box Functions A and B in AWS Lambda

What happens if we need to move the Function B code to another infrastructure? Perhaps the AWS Lambda memory limits aren't enough anymore.

The AWS SDK invocation process will not work anymore. In order to migrate Function B , a concomitant modification is required in Function A .

Black-box AWS SDK fails

This type of architecture makes it difficult to introduce changes and increases the likelihood of unintended consequences.

The problem here is that Function B is leaking its implementation details, thus not working as a Black-box. The leakage is having Function A aware that Function B is deployed in AWS Lambda.

To satisfy the Black Box principle, we could create an HTTP API. By routing requests through the API, we can decouple the functions. Later we can just update the API routing to the new infrastructure when migrating Function B.

This simplifies change, which can be extremely valuable as the system and development team grows.

Black-box - decoupling with an HTTP API

Double-billing

This principle is relatively simple: two functions should not run at the same time while one is in idle state waiting for the other.

In Serverless, we pay for the function execution time. Consider Function A starts processing and being billed. At some point, it invokes Function B and waits for a response.

Problem is: While B is processing, A continues to be charged, despite being idle. Function B processing time is effectively being charged twice, which is arguably undesirable.

Double Billing Animated Gif

Substitution

Substitution is about having a composition of functions behaving just like any normal function.

But what is composition?

Consider a user requests an invoice from a billing system, which is served by a serverless function. This function may rely on a second one to apply any discounts available to this user. Yet another function is responsible for calculating Sales Tax, for instance.

All three functions are composed to work as a group, hence the name "composition".

Functions Composition

The substitution principle indicates that all three functions together should behave like a function. From invocation methods to timeout to dead-letter-queue, everything behaves as if they were a single function.

The requester doesn't actually know this is a composition of functions. Any developer can rely on this composition without worrying about how it behaves, they just apply everything they already know.

This property improves productivity and overall quality by reducing errors and unintended consequences.

Functions Composition behaves as a single function

Trade-offs involved

Most of the time, it is not possible to have all the properties together. Applying the Black box principle is usually easy. When it comes to the other two, there is usually a trade-off.

When we prioritize one, such as Substitution it becomes difficult to apply Double-billing.

Trade-off animated gif

The Serverless Trilemma framework helps to make architectural decisions and identify what needs to be prioritized:

  • Black Box improves maintainability, scalability, and resilience by decoupling components
  • Double-billing enables cost-efficiency and reduces the overall concurrency level of the system
  • Substitution enables code reusability and improves the quality

What should be prioritized will really depend on each case. We'll dive into more details about this decision-making process in the next articles coming on this series.

To stay tuned, subscribe here for free to receive announcements when we publish again.

This post is also available on DEV.