The pervasive adoption of connected digital systems in modern society (as in cloud and mobile computing) has projected us towards the “API economy”: a market of digital services that can be accessed on networks through Application Programming Interfaces (APIs). To ensure that these systems are efficient and reliable, the best practice is to deploy dedicated software components (microservices) that act as proxies between clients and services [Chandramouli, 2019; Montesi and Weber, 2016].
The implementation of this strategy is a challenge for the theory of types and programming languages. Ideally, we should program modular proxies that can be reused and combined in different contexts, by abstracting from the APIs that will be managed. These proxies typically alter the APIs that are ultimately exposed to clients, e.g., by adding required fields (like a password) and effects (like an exception in case of wrong passwords). Once a proxy implementation is used with some concrete APIs, the result of its API modifications should be computed statically (before execution), such that client developers can verify their clients against the actual APIs that the proxy will expose. Unfortunately, current programming tools do not properly support this ideal methodology, due to the lack of language abstractions for manipulating abstract APIs in proxy programs.
The goal of this project is to establish the scientific foundations for the ideal development methodology described above. Specifically, we wish to program generic microservices: proxy microservices that can be reused with different APIs and allow for statically computing the resulting APIs exposed to clients.
The hypothesis is that a programming theory for generic microservices can be achieved by developing the first algebra of APIs, an abstract model of how common patterns for API management manipulate APIs. The abstract operators of our algebra are going to act as guidelines for reasoning on (i) how reusable proxies can be coded without knowing the APIs that they will guard, and (ii) how we can statically compute the resulting APIs once a generic microservice is applied to a specific context.
We plan to:
As an example of what we want to be able to program, consider a circuit breaker proxy: a microservice that guards another service to prevent overload (including malevolent denial of service attacks). The idea is that a circuit breaker should be parameterised over the API that it guards, obtaining a generic implementation like the one depicted below.
A key property that we desire is substitutability: given a concrete service, it should be possible to deploy the generic circuit breaker to guard it and statically compute the resulting API. For example, as depicted below, the same generic circuit breaker implementation should be applicable to two (or more) different services with difference APIs (here we have an HVAC service and an analytics service).
Another key property that we wish for is being able to reason abstractly about compositions of generic microservices. For example, given a generic circuit breaker and a generic authorisation proxy (which authorises invocations), we should be able to compose them in any order and be able to compute statically the resulting modification on the APIs that they could guard. Below we depict an example of commutativity for these two generic microservices.
In our work, we will be inspired by the catalogue of microservice API patterns and other design patterns for microservices [Montesi and Weber, 2016]. We plan on using our language implementation to construct a catalogue of generic microservices that implement these patterns and make them available on public repositories (e.g., Docker Hub).