There are many definitions and meanings to what we mean by the words "software architecture". There is also a sizeable overlap between what constitutes "software development", "software design", and "software architecture" as the three concepts converge in many ways.
At its core, it helps to view the discipline of software architecture as the discipline of making conscious choices between the tradeoffs that result from the choices we make when building an application this or that way.
Why Are There Tradeoffs and Why Do We Care?
The reason why we must make tradeoffs when making choices in how we build software is the same reason that we have tradeoffs in other disciplines. Computing systems are complex, and the higher the complexity, the more nuanced is the way by which we can achieve the full spectrum of objectives for a particular system. These objectives can be functional (i.e. providing an ability for users to self-serve, process specific events, accept some input and produce output) as well as non-functional (i.e. ability to process millions of requests per second, achieving zero-trust security, sub 100ms responses).
For example, in financial investing, there is an inherent understanding of a tradeoff between risk and reward. The riskier an investment is, the larger its potential financial reward. The safer the investment, the smaller is the gain we can anticipate from that investment.
The same rule applies to computing - especially when architecting distributed applications. The problem that many organizations encounter is not that they must make certain concessions or tradeoffs. The problem is that most organizations are either unaware of the tradeoffs they are making or lack a systematic, clear, and effective way to be making these tradeoffs.
The aim of this “Architecture Tradeoffs” series will be to shed some light on the decision making process when it comes to tradeoffs between different tenets of software architecture as well as the concrete technical implications of such decisions.
What Are We Trading Off?
As we’ve seen above, most decisions related to the architecture of your systems and applications involve some level of tradeoffs.
Now that we are aware of why architectural tradeoffs are something that needs to be discussed, weighed, and consciously reasoned about, we can talk about what aspects of our systems and applications we are actually trading off.
These architectural tradeoffs are sometimes classified in two main categories. The first is related to decisions about the foundational architectural characteristics of your system (ie scalability vs simplicity). The second is related to decisions on more concrete technologies, mechanisms, and architectural styles (ie synchronous communication vs asynchronous, Kafka vs Message Bus, etc). The former, more general category of tradeoffs, also dictates the more concrete tradeoffs.
In this article, we will focus on the first category of architectural tradeoffs - that of the foundational architectural characteristics.
So when we talk about architectural tradeoffs, we really are talking about which architectural characteristics we want to support and roll into our primary objective. The other side of the coin is that we are also identifying those architectural characteristics that we are consciously deciding to focus less on or forego altogether in favour of those characteristics which we deem more important.
Below are examples of several architectural characteristics and some common tradeoff scenarios.
Architectural Characteristics
When talking about the inherent or foundational characteristics of a system or application’s architecture, we are really referring to a collection of key properties that characterize a particular system or application. Here is a small subset of these characteristics as an example. This is not an exhaustive list by any means. There are many other architectural characteristics
Scalability
Observability
Auditability
Resilience
Responsiveness
Testability
Interoperability
Maintainability
Supportability
These characteristics sometimes go by “system properties”, “architectural attributes” or are often simply called “ilities”.
These system characteristics or attributes seem independent of each other at first glance. In reality though, many of them are very much intertwined and can have either a direct or an inverse relationship.
Interoperability vs Scalability, Elasticity and Responsiveness
Let’s take interoperability as an example. In order for a system to be interoperable, it needs to have the ability to easily interact and communicate with other systems. This will typically mean that all of these systems need to speak a common protocol. They need to use common and agreed upon standards, which are also built in such a way that future systems can also “plug-in” into that communication with relative ease.
However, if interoperability is a priority for a system, there is a high likelihood that this will affect the system’s ability to scale.
To bring this all down to a concrete example, consider a scenario where you have some existing applications that use the REST protocol (which relies on HTTP) to communicate. Say we are introducing a new system. In order to make this new application interoperable with the ones that already exist, we decide that all of this application’s inbound and outbound communication will also be done via REST/HTTP. It seems to make sense.
However, limiting your new system to communication that relies on HTTP may be limiting both its responsiveness and its scalability if it is expected to process large volumes of requests. Imagine that this new system is bound to process millions of requests per second from that caller and is also asynchronous (i.e. the caller does not need to wait for the application to acknowledge that it has processed the request). This scenario might lend itself much better to an event-driven technology such as Kafka due to both the requirement for asynchrony and since the proprietary Kafka protocol has (at least in theory) less overhead than HTTP.
In other words, in this particular scenario, interoperability has an inverse relationship to responsiveness as well as to scalability.
Simplicity, Ease of Onboarding and Supportability vs Responsiveness
Another, perhaps, less technical and more organizational tradeoff happens when deciding on an architecture that is relatively easy to support as the primary architectural driver. This can mean using technologies that are familiar to the technical teams within the organization. It can also mean using technologies and paradigms that are widely known and used in the industry so that new team members can start being productive with them quicker and more efficiently.
Putting supportability as first priority sounds like a no-brainer as who doesn’t want a system that is easy to understand, support, and introduce to new developers? As always though, there is a cost and a tradeoff.
Choosing a technology or a paradigm on the basis of them being known by a large base of professionals may impede many other characteristics of our architecture. Scalability, responsiveness, elasticity, availability, security, and many other aspects of the system will most likely take second priority.
For instance, consider a case where there is a need to design a financial application that needs to process and store transactional and strongly relational data. Let’s imagine that the team implementing this application is familiar with NoSQL data stores such as MongoDB. Now, although Mongo is a great choice for loosely related Document-type data, it does not lend itself well to data with strict and complex interrelationships.
Data where different entities have complex relationships and where at-hoc complex queries are required to retrieve that data would typically not be the best fit for something like Mongo. This type of data would typically fit much better a “traditional” relational database such as Postgres or MySQL.
If we had put supportability as the main architectural driver for this application, it would most likely set up the application for failure down the line and would introduce a host of challenges eventually leading to the application’s complete inability to scale and the database becoming a show-stopper bottleneck (as it often is).
Maintainability vs Resilience and Fault Tolerance
All else being equal, the less moving parts and the simpler the system is, the easier it is to maintain. The less technologies and deployment environments are used, the faster and simpler it is to keep the system running and well-oiled.
For example, an application running in a single instance in a managed cloud runtime service is much easier to maintain than a distributed cluster of interconnected and heterogenous applications that use different deployment mechanisms, different network topologies, and different runtimes.
Fault tolerant (near zero downtime) systems are typically deployed across different nodes, in different clusters, cloud zones, and regions. Some organizations go as far as to ensure business continuity, fault tolerance, and disaster recovery by deploying their systems across multiple cloud environments (the rapidly rising in popularity, albeit somewhat controversial, multi-cloud model).
Since each cloud (Azure, AWS, GCP, IBM, Oracle, and others) provider has a unique set of features, deployment models, and mechanisms unique to that cloud, maintaining an ecosystem of applications in working order on multiple clouds becomes a significant challenge (often unjustifiable) for the maintainability of these systems.
Finding the Balance
The three examples of architectural tradeoffs above may touch more on the extremes. However, they are representative of some very real challenges in planning and selecting the correct course of navigating architectural tradeoffs in many teams and organizations.
The good news is that you do not necessarily have to choose either one or the other. The reality of software architecture tradeoffs, and software development in general, is a lot more nuanced and really represents a gradient of options. This is where, for example, you can choose to have a degree of scalability and at the same time a degree of simplicity and interoperability.
The key is to know how to find that balance between different architectural characteristics and to know how to make well-informed conscious choices about these characteristics.
Being Conscious and Aware of Architectural Tradeoffs
As we said in the beginning, many, dare I say - most organizations, choose tradeoffs unconsciously. This often leads to these organizations making the wrong tradeoffs, which has long lasting and detrimental implications to their business and bottom lines.
Businesses that are driven by digital systems must have a proper plan and process in place for making software architecture and technical decisions and tradeoffs. Without creating the proper awareness around architectural tradeoffs, these organizations assume an unjustified risk that is large in both its impact and probability of significantly slowing down the organization’s progress in the best case and damaging it beyond repair in the worst case.
Next instalments will discuss just that - how to go about reasoning and planning for architectural tradeoffs as well as some specific and common situations.
Next: Software Architecture Tradeoffs Series - Part 2 - The Problem with Making Unconscious Decisions
Comments