Assert Autonomy: The Reactive Principles, Explained
Design components that act independently and interact collaboratively
It sounds like a lot of work to act independently and interact collaboratively. This is perhaps a goal that many of us aspire to as humans; however, achieving this in computing pays much less attention to philosophical and emotional needs–only system architecture and code needs to follow this fourth principle of The Reactive Principles: Assert Autonomy, elucidated upon in this article by our guest author, Dr. Roland Kuhn.
In this explanatory series, we look at The Reactive Principles in detail and go deeper into the meaning of things. You can refer to the original document for full context as well as these excerpts and additional discussion.
Autonomy within a larger system
The components of a larger system can only stay responsive to the degree of autonomy they have from the rest of the system. In Reactive applications, autonomy is achieved by clearly defining the component boundaries, who owns what data and how the owners make it available, and by designing them such that each party is afforded the necessary degree of freedom to make its own decisions.
Which kind of autonomy are we talking about?
From an end-user’s perspective any degradation is annoying, but what is most irritating is when something doesn’t work that should be unaffected by the outage that is currently ongoing as per the status page.
An example that illustrates this quite well is the outage of Google’s User ID service on
Dec 14, 2020: this conservatively programmed security-relevant service was used by many other websites and services, many of which didn’t need the provided security guarantees. As a result, it was impossible to view clips on youtube even though that operation does not fundamentally require authentication or authorization at all — as witnessed by the fact that opening the page in an anonymous browser tab worked perfectly.
Autonomy means that one thing (person, nation state, microservice, … ) can continue to function independently of some other thing. In the above example, youtube could have kept showing clips to users whose identity momentarily couldn’t be ascertained.
The contrary of autonomy is when one thing crucially depends on another, when it cannot possibly function without it. In the example above it would be unwise to make all of youtube autonomous from the User ID service as comments and uploads should better come from a securely identified user account.
Constraints liberate, liberties constrain
Another aspect of autonomy is that the boundary between the two components is crossed only via the documented protocols; there cannot be other side-channels. Only with this discipline is it possible to reason about the collaboration and potentially verify it formally (for example using Session Types).
Do fixed protocols not rather take freedom and autonomy away?
The title of this section is taken from the seminal presentation by Rúnar Bjarnason: he applies this seemingly contradictory logic to the types in a programming language, but the same principle is valid for the cooperation and collaboration between software components in a larger reactive system.
The apparent contradiction between taking away freedom and gaining autonomy resolves when considering expectation management. If there is no expectation management, and I ask you a yes/no question, then I’ll be at the mercy of your whims. Maybe there will be an answer that I can understand, but maybe not — your liberty greatly constrains my further progress.
If on the other hand there is a clear expectation that such a question must be quickly answered with yes or no, then I can confidently await that response and plan ahead — now my liberty is bought by constraining your freedom. A more balanced protocol would allow you to respond with “not now”, giving us both the ability to pursue our own plans and stay autonomous.
In software terms, it is immensely useful to agree on expectations when designing the code because this takes away the need to dynamically agree later when the programs are being run. Considering how much human discussion skill it takes to agree during the design phase it should be obvious that it takes a quite skillful program to perform that task at runtime.
DDD, Event Sourcing, and CQRS
Valuable patterns that foster autonomy include: domain-driven design, event-sourcing, and CQRS. Communicating fully self-contained facts — modeled closely after the underlying business domain — gives the recipient the power to make their own decisions without having to ask again for more information. CQRS separates concerns by making decisions about one part of the system in one location (one component), which can then readily be disseminated and acted upon elsewhere—possibly at a much later time.
What are example cases where acting later and somewhere else are useful?
When our code and business logic makes decisions we record this information as events or in a database, we store it so that we can recall it. However, most data we deal with is read much more often than it is written — we revisit past decisions to compute consequences, summarise them, create reports from them, audit them.
A classical example of this is transaction processing in any e-commerce application, where incoming customer decisions are validated in the write-side of the system against authentication data, credit cards, warehouse state, etc. The stream of processing results is then used by various read-sides to kick off delivery processes, send emails, but also perform statistical analysis on what is popular and how popularities correlate.
In all these usages, the consumer benefits from facts being recorded with clear semantics, this way the information is useful beyond the initial imagination and design of the write-side.
This becomes even more important in heterogeneous systems that are dynamically composed in an ad hoc fashion for local-first cooperation: based on clearly formulated expectations by way of communication protocols we can for example bring together machines made by different builders on a factory’s shop-floor and have them cooperate based on each one reading and then acting upon the decisions made and recorded by others.