Chapter 1. Software Architecture
Developers have long struggled to coin a succinct, concise definition of software architecture because the scope is large and ever-changing. Ralph Johnson famously defined software architecture as “the important stuff (whatever that is).” The architect’s job is to understand and balance all of those important things (whatever they are).
An initial part of an architect’s job is to understand the business or domain requirements for a proposed solution. Though these requirements operate as the motivation for utilizing software to solve a problem, they are ultimately only one factor that architects should contemplate when crafting an architecture. Architects must also consider numerous other factors, some explicit (e.g., performance service-level agreements) and others implicit to the nature of the business (e.g., the company is embarking on a mergers and acquisition spree). Therefore, the craft of software architecture manifests in the ability of architects to analyze business and domain requirements along with other important factors to find a solution that balances all concerns optimally. The scope of software architecture is derived from the combination of all these architectural factors, as shown in Figure 1-1.
As seen in Figure 1-1, business and domain requirements exist alongside other architecture concerns (defined by architects). This includes a wide range of external factors that can alter the decision process on what and how to build a software system. For a sampling, check out the list in Table 1-1:
accessibility |
accountability |
accuracy |
adaptability |
administrability |
affordability |
agility |
auditability |
autonomy |
availability |
compatibility |
composability |
configurability |
correctness |
credibility |
customizability |
debugability |
degradability |
determinability |
demonstrability |
dependability |
deployability |
discoverability |
distributability |
durability |
effectiveness |
efficiency |
usability |
extensibility |
failure transparency |
fault tolerance |
fidelity |
flexibility |
inspectability |
installability |
integrity |
interoperability |
learnability |
maintainability |
manageability |
mobility |
modifiability |
modularity |
operability |
orthogonality |
portability |
precision |
predictability |
process capabilities |
producibility |
provability |
recoverability |
relevance |
reliability |
repeatability |
reproducibility |
resilience |
responsiveness |
reusability |
robustness |
safety |
scalability |
seamlessness |
self-sustainability |
serviceability |
securability |
simplicity |
stability |
standards compliance |
survivability |
sustainability |
tailorability |
testability |
timeliness |
traceability |
When building software, architects must determine the most important of these “-ilities.” However, many of these factors oppose one another. For example, achieving both high performance and extreme scalability can be difficult because achieving both requires a careful balance of architecture, operations, and many other factors. As a result, the necessary analysis in architecture design and the inevitable clash of competing factors requires balance, but balancing the pros and cons of each architectural decision leads to the tradeoffs so commonly lamented by architects. In the last few years, incremental developments in core engineering practices for software development have laid the foundation for rethinking how architecture changes over time and on ways to protect important architectural characteristics as this evolution occurs. This book ties those parts together with a new way to think about architecture and time.
We want to add a new standard “-ility” to software architecture—evolvability.
Evolutionary Architecture
Despite our best efforts, software becomes harder to change over time. For a variety of reasons, the parts that comprise software systems defy easy modification, becoming more brittle and intractable over time. Changes in software projects are usually driven by a reevaluation of functionality and/or scope. But another type of change occurs outside the control of architects and long-term planners. Though architects like to be able to strategically plan for the future, the constantly changing software development ecosystem makes that difficult. Since we can’t avoid change, we need to exploit it.
How Is Long-term Planning Possible When Everything Changes All the Time?
In the biological world, the environment changes constantly from both natural and man-made causes. For example, in the early 1930s, Australia had problems with cane beetles, which rendered the production and harvesting sugar cane crops less profitable. In response, in June 1935, the then Bureau of Sugar Experiment Stations introduced a predator, the cane toad, previously only native to south and middle America.1 After being bred in captivity a number of young toads were released in North Queensland in July and August 1935. With poisonous skin and no native predators, the cane toads spread widely; there are an estimated 200 million in existence today. The moral: introducing changes to a highly dynamic (eco)system can yield unpredictable results.
The software development ecosystem consists of all the tools, frameworks, libraries, and best practices—the accumulated state of the art in software development. This ecosystem forms an equilibrium—much like a biological system—that developers can understand and build things within. However, that equilibrium is dynamic—new things come along constantly, initially upsetting the balance until a new equilibrium emerges. Visualize a unicyclist carrying boxes: dynamic because she continues to adjust to stay upright and equilibrium because she continues to maintain balance. In the software development ecosystem, each new innovation or practice may disrupt the status quo, forcing the establishment of a new equilibrium. Metaphorically, we keep tossing more boxes onto the unicyclist’s load, forcing her to reestablish balance.
In many ways, architects resemble our hapless unicyclist, constantly both balancing and adapting to changing conditions. The engineering practices of Continuous Delivery represent such a tectonic shift in the equilibrium: Incorporating formerly siloed functions such as operations into the software development lifecycle enabled new perspectives on what change means. Enterprise architects can no longer rely on static 5-year plans because the entire software development universe will evolve in that timeframe, rendering every long-term decision potentially moot.
Disruptive change is hard to predict even for savvy practitioners. The rise of containers via tools like Docker is an example of an unknowable industry shift. However, we can trace the rise of containerization via a series of small, incremental steps. Once upon a time, operating systems, application servers, and other infrastructure were commercial entities, requiring licensing and great expense. Many of the architectures designed in that era focused on efficient use of shared resources. Gradually, Linux became good enough for many enterprises, reducing the monetary cost of operating systems to zero. Next, DevOps practices like automatic machine provisioning via tools like Puppet or Chef made Linux operationally free. Once the ecosystem became free and widely used, consolidation around common portable formats was inevitable; thus, Docker. But containerization couldn’t have happened without all the evolutionary steps leading to that end.
The programming platforms we use exemplify constant evolution. Newer versions of a programming language offer better application programming interfaces (APIs) to improve the flexibility or applicability toward new problems; newer programming languages offer a different paradigm and different set of constructs. For example, Java was introduced as a C++ replacement to ease the difficulty of writing networking code and to improve memory management issues. When we look at the past 20 years, we observe that many languages still continually evolve their APIs while totally new programming languages appear to regularly attack newer problems. The evolution of programming languages is demonstrated in Figure 1-2.
Regardless of which particular aspect of software development—the programming platform, languages, the operating environment, persistence technologies, and so on—we expect constant change. Although we cannot predict when changes in the technical or domain landscape will occur, or which changes will persist, we know change is inevitable. Consequently, we should architect our systems knowing the technical landscape will change.
If the ecosystem constantly changes in unexpected ways, and predictability is impossible, what is the alternative to fixed plans? Enterprise architects and other developers must learn to adapt. Part of the traditional reasoning behind making long-term plans was financial; software changes were expensive. However, modern engineering practices invalidate that premise by making change less expensive by automating formerly manual processes and other advances such as DevOps.
For years, many smart developers recognized that some parts of their systems were harder to modify than others. That’s why software architecture is defined as the “parts hard to change later.” This convenient definition partitioned the things you can modify without much effort from truly difficult changes. Unfortunately, this definition also evolved into a blind spot when thinking about architecture: Developers’ assumption that change is difficult becomes a self-fulfilling prophecy.
Several years ago, some innovative software architects revisited the “hard to change later” problem in a new light: what if we build changeability into the architecture? In other words, if ease of change is a bedrock principle of the architecture, then change is no longer difficult. Building evolvability into architecture in turn allows a whole new set of behaviors to emerge, upsetting the dynamic equilibrium again.
Even if the ecosystem doesn’t change, what about the gradual erosion of architectural characteristics that occurs? Architects design architectures, but then expose them to the messy real world of implementing things atop the architecture. How can architects protect the important parts they have defined?
Once I’ve Built an Architecture, How Can I Prevent It from Gradually Degrading Over Time?
An unfortunate decay, often called bit rot, occurs in many organizations. Architects choose particular architectural patterns to handle the business requirements and “-ilities,” but those characteristics often accidentally degrade over time. For example, if an architect has created a layered architecture with presentation at the top, persistence at the bottom, and several layers in between, developers who are working on reporting will often ask permission to directly access persistence from the presentation layer, bypassing the other layers, for performance reasons. Architects build layers to isolate change. Developers then bypass those layers, increasing coupling and invalidating the reasoning behind the layers.
Once they have defined the important architectural characteristics, how can architects protect those characteristics to ensure they don’t erode? Adding evolvability as an architectural characteristics implies protecting the other characteristics as the system evolves. For example, if an architect has designed an architecture for scalability, she doesn’t want that characteristic to degrade as the system evolves. Thus, evolvability is a meta-characteristic, an architectural wrapper that protects all the other architectural characteristics.
In this book, we illustrate that a side effect of an evolutionary architecture is mechanisms to protect the important architecture characteristics. We explore the ideas behind continual architecture: building architectures that have no end state and are designed to evolve with the ever-changing software development ecosystem, and including built-in protections around important architectural characteristics. We don’t attempt to define software architecture in totality; many other definitions exist. We focus instead on extending current definitions to adding time and change as first-class architectural elements.
Here is our definition of evolutionary architecture:
An evolutionary architecture supports guided, incremental change across multiple dimensions.
Incremental Change
Incremental change describes two aspects of software architecture: how teams build software incrementally and how they deploy it.
During development, an architecture that allows small, incremental changes is easier to evolve because developers have a smaller scope of change. For deployment, incremental change refers to the level of modularity and decoupling for business features and how they map to architecture. An example is in order.
Let’s say that PenultimateWidgets, a large seller of widgets, has a catalog page backed by a microservice architecture and modern engineering practices. One of the page’s features is the ability of users to rate different widgets with star ratings. Other services within PenultimateWidgets’ business also need ratings (customer service representatives, shipping provider evaluation, and so on), so they all share the star rating service. One day, the star rating team releases a new version alongside the existing one that allows half-star ratings—a small but significant upgrade. The other services that require ratings aren’t required to move to the new version, but rather gradually migrate as convenient. Part of PenultimateWidgets’ DevOps practices include architectural monitoring of not only the services but also the routes between services. When the operations group observes that no one has routed to a particular service within a given time interval, they automatically disintegrate that service from the ecosystem.
This is an example of incremental change at the architectural level: the original service can run alongside the new one as long as other services need it. Teams can migrate to new behavior at their leisure (or as need dictates), and the old version is automatically garbage collected.
Making incremental change successful requires coordination of a handful of Continuous Delivery practices. Not all these practices are required in all cases but rather commonly occur together in the wild. We discuss how to achieve incremental change in Chapter 3.
Guided Change
Once architects have chosen important characteristics, they want to guide changes to the architecture to protect those characteristics. For that purpose, we borrow a concept from evolutionary computing called fitness functions. A fitness function is an objective function used to summarize how close a prospective design solution is to achieving the set aims. In evolutionary computing, the fitness function determines whether an algorithm has improved over time. In other words, as each variant of an algorithm is generated, the fitness functions determine how “fit” each variant is based on how the designer of the algorithm defined “fit.”
We have a similar goal in evolutionary architecture—as architecture evolves, we need mechanisms to evaluate how changes impact the important characteristics of the architecture and prevent degradation of those characteristics over time. The fitness function metaphor encompasses a variety of mechanisms we employ to ensure architecture doesn’t change in undesirable ways, including metrics, tests, and other verification tools. When an architect identifies an architectural characteristic they want to protect as things evolve, they define one or more fitness functions to protect that feature.
Historically, a portion of architecture has often been viewed as a governance activity, and architects have only recently accepted the notion of enabling change through architecture. Architectural fitness functions allow decisions in the context of the organization’s needs and business functions, while making the basis for those decisions explicit and testable. Evolutionary architecture is not an unconstrained, irresponsible approach to software development. Rather, it is an approach that balances the need for rapid change and the need for rigor around systems and architectural characteristics. The fitness function drives architectural decision making, guiding the architecture while allowing the changes needed to support changing business and technology environments.
We use fitness functions to create evolutionary guidelines for architectures; we cover them in detail in Chapter 2.
Multiple Architectural Dimensions
There are no separate systems. The world is a continuum. Where to draw a boundary around a system depends on the purpose of the discussion.
Donella H. Meadows
Classical Greek physics gradually learned to analyze the universe based on fixed points, culminating in Classical Mechanics. However, more precise instruments and more complex phenomena gradually refined that definition toward relativity in the early 20th century. Scientists realized that what they previously viewed as isolated phenomenon in fact interact relative to one another. Since the 1990s, enlightened architects have increasingly viewed software architecture as multidimensional. Continuous Delivery expanded that view to encompass operations. However, software architects often focus primarily on technical architecture, but that is only one dimension of a software project. If architects want to create an architecture that can evolve, they must consider all parts of the system that change affects. Just like we know from physics that everything is relative to everything else, architects know there are many dimensions to a software project.
To build evolvable software systems, architects must think beyond just the technical architecture. For example, if the project includes a relational database, the structure and relationship between database entities will evolve over time as well. Similarly, architects don’t want to build a system that evolves in a manner that exposes a security vulnerability. These are all examples of dimensions of architecture—the parts of architecture that fit together in often orthogonal ways. Some dimensions fit into what are often called architectural concerns (the list of “-ilities” above), but dimensions are actually broader, encapsulating things traditionally outside the purview of technical architecture. Each project has dimensions the architect must consider when thinking about evolution. Here are some common dimensions that affect evolvability in modern software architectures:
- Technical
-
The implementation parts of the architecture: the frameworks, dependent libraries, and the implementation language(s).
- Data
-
Database schemas, table layouts, optimization planning, etc. The database administrator generally handles this type of architecture.
- Security
-
Defines security policies, guidelines, and specifies tools to help uncover deficiencies.
- Operational/System
-
Concerns how the architecture maps to existing physical and/or virtual infrastructure: servers, machine clusters, switches, cloud resources, and so on.
Each of these perspectives forms a dimension of the architecture—an intentional partitioning of the parts supporting a particular perspective. Our concept of architectural dimensions encompasses traditional architectural characteristics (“-ilities”) plus any other role that contributes to building software. Each of these forms a perspective on architecture that we want to preserve as our problem evolves and the world around us changes.
A variety of partitioning techniques exist for conceptually carving up architectures. For example, the 4 + 1 architecture View Model, which focuses on different perspectives from different roles and was incorporated into the IEEE definition of software architecture, splits the ecosystem into logical, development, process, and physical views. In the well-known book Software Systems Architecture, the authors posit a catalog of viewpoints on software architecture, spanning a larger set of roles. Similarly, Simon Brown’s C4 notation partitions concerns for aid in conceptual organization. In this text, in contrast, we don’t attempt to create a taxonomy of dimensions but rather recognize the ones extant in existing projects. Pragmatically, regardless of which category a particular important concern falls into, the architect must still protect that dimension. Different projects have differing concerns, leading to unique sets of dimensions for a given project. Any of these techniques provide useful insight, particularly in new projects, but existing projects must deal with the realities of what exists.
When architects think in terms of architectural dimensions, it provides a mechanism by which they can analyze the evolvability of different architectures by assessing how each important dimension reacts to change. As systems become more intertwined with competing concerns (scalability, security, distribution, transactions, and so on), architects must expand the dimensions they track on projects. To build an evolvable system, architects must think about how the system might evolve across all the important dimensions.
The entire architectural scope of a project consists of the software requirements plus the other dimensions. We can use fitness functions to protect those characteristics as the architecture and the ecosystem evolve together through time, as illustrated in Figure 1-3.
In Figure 1-3, the architects have identified auditability, data, security, performance, legality, and scalability as the additional architectural characteristics important for this application. As the business requirements evolve over time, each of the architectural characteristics utilize fitness functions to protect their integrity as well.
While the authors of this text stress the importance of a holistic view of architecture, we also realize that a large part of evolving architecture concerns technical architecture patterns and related topics like coupling and cohesion. We discuss how technical architecture coupling affects evolvability in Chapter 4 and the impacts of data coupling in Chapter 5.
Coupling applies to more than just structural elements in software projects. Many software companies have recently discovered the impact of team structure on surprising things like architecture. We discuss all aspects of coupling in software, but the team impact comes up so early and often that we need to discuss it here.
Conway’s Law
In April 1968, Melvin Conway submitted a paper to Harvard Business Review called, “How Do Committees Invent?”. In this paper, Conway introduced the notion that the social structures, particularly the communication paths between people, inevitably influence final product design.
As Conway describes, in the very early stage of the design, a high-level understanding of the system is made to understand how to break down areas of responsibility into different patterns. The way that a group breaks down a problem affects choices that they can make later. He codified what has become known as Conway’s Law:
Organizations which design systems … are constrained to produce designs which are copies of the communication structures of these organizations.
Melvin Conway
As Conway notes, when technologists break down problems into smaller chunks to delegate, we introduce coordination problems. In many organizations, formal communication structures or rigid hierarchy appear to solve this coordination problem but often lead to inflexible solutions. For example, in a layered architecture where the team is separated by technical function (user interface, business logic, and so on), solving common problems that cut vertically across layers increases coordination overhead. People who have worked in startups and then have joined large multinational corporations have likely experienced the contrast between the nimble, adaptable culture of the former and the inflexible communication structures of the latter. A good example of Conway’s Law in action might be trying to change the contract between two services, which could be difficult if the successful change of a service owned by one team requires the coordinated and agreed-upon effort of another.
In his paper, Conway was effectively warning software architects to pay attention not only to the architecture and design of the software, but also the delegation, assignment, and coordination of the work between teams.
In many organizations teams are divided according to their functional skills. Some common examples include:
- Front-end developers
-
A team with specialized skills in a particular user interface (UI) technology (e.g., HTML, mobile, desktop).
- Back-end developers
-
A team with unique skills in building back-end services, sometimes API tiers.
- Database developers
-
A team with unique skills in building storage and logic services.
In organizations with functional silos, management divides teams to make their Human Resources department happy without much regard to engineering efficiency. Although each team may be good at their part of the design (e.g., building a screen, adding a back-end API or service, or developing a new storage mechanism), to release a new business capability or feature, all three teams must be involved in building the feature. Teams typically optimize for efficiency for their immediate tasks rather than the more abstract, strategic goals of the business, particularly when under schedule pressure. Instead of delivering an end-to-end feature value, teams often focus on delivering components that may or may not work well with each other.
In this organizational split, a feature dependent on all three teams takes longer as each team works on their component at different times. For example, consider the common business change of updating the Catalog page. That change entails the UI, business rules, and database schema changes. If each team works in their own silo, they must coordinate schedules, extending the time required to implement the feature. This is a great example of how team structure can impact architecture and the ability to evolve.
As Conway noted in his paper by noting every time a delegation is made and somebody’s scope of inquiry is narrowed, the class of design alternatives which can be effectively pursued is also narrowed. Stated another way, it’s hard for someone to change something if the thing she wants to change is owned by someone else. Software architects should pay attention to how work is divided and delegated to align architectural goals with team structure.
Many companies who build architectures such as microservices structure their teams around service boundaries rather than siloed technical architecture partitions. In the ThoughtWorks Technology Radar, we call this the Inverse Conway Maneuver. Organization of teams in such a manner is ideal because team structure will impact myriad dimensions of software development and should reflect the problem size and scope. For example, when building a microservices architecture, companies typically structure teams that resemble the architecture by cutting across functional silos and including team members who cover every angle of the business and technical aspects of the architecture.
Tip
Structure teams to look like your target architecture, and it will be easier to achieve it.
Team impact shows up in many places throughout the book, with examples of how many consequences it has.
Why Evolutionary?
A common question about evolutionary architecture concerns the name itself: why call it evolutionary architecture and not something else? Other possible names include incremental, continual, agile, reactive, and emergent, to name just a few. But each of these terms misses the mark here. The definition of evolutionary architecture that we state here includes two critical characteristics: incremental and guided.
The terms continual, agile, and emergent all capture the notion of change over time, which is clearly a critical characteristic of an evolutionary architecture, but none of these terms explicitly capture any notion of how an architecture changes or what the desired end state architecture might be. While all the terms imply a changing environment, none of them cover what the architecture should look like. The guided part of our definition reflects the architecture we want to achieve—our end goal.
We prefer the word evolutionary over adaptable because we are interested in architectures that undergo fundamental evolutionary change, not ones that have been patched and adapted into increasingly incomprehensible accidental complexity. Adapting implies finding some way to make something work regardless of the elegance or longevity of the solution. To build architectures that truly evolve, architects must support genuine change, not jury-rigged solutions. Going back to our biological metaphor, evolutionary is about the process of having a system that is fit for purpose and can survive the ever-changing environment in which it operates. Systems may have individual adaptations, but as architects, we should care about the overall evolvable system.
Summary
An evolutionary architecture consists of three primary aspects: incremental change, fitness functions, and appropriate coupling. In the remainder of the book, we discuss each of these factors separately, then combine them to address what it takes to build and maintain architectures that support constant change.
1 Clarke, G. M., Gross, S., Matthews, M., Catling, P. C., Baker, B., Hewitt, C. L., Crowther, D., & Saddler, S. R. 2000, Environmental Pest Species in Australia, Australia: State of the Environment, Second Technical Paper Series (Biodiversity), Department of the Environment and Heritage, Canberra.
Get Building Evolutionary Architectures now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.