- When software architecture evolves through a series of decisions, development teams need a way to keep track of the architectural decisions they have made. They usually record each decision as an Architectural Decision Record (ADR).
- The boundary between architectural and other significant decisions is often ill-defined.
- All architectural decisions are significant (using the cost of change to measure significance) but not all significant decisions are architectural.
- Just because something is time-consuming to change does not make it architectural.
- Architectural decisions involve the fundamental concepts the system uses because the code implications of the choices are scattered throughout the software rather than being localized.
- Teams who need to record significant decisions should create a separate Significant Decision Record to avoid overburdening their ADR with other decisions.
Architectural Decision Records (ADRs) are important vehicles for communicating the architectural decisions a development team makes about a system. Lacking a clear definition of what is architectural, and also lacking anywhere else to record important decisions, they can start to drift from their original purpose and lose focus and effectiveness.
ADRs are intended to expose architectural decisions, and in so doing improve transparency and accountability. But when it becomes bloated with every decision a team makes, it becomes the antithesis because the architectural decisions can’t be easily seen amidst everything else that’s been thrown into an ADR.
Why are ADRs needed?
In a previous article, we observed that in a dynamic software development approach, in which solutions evolve over time (e.g. agile), software architecture is defined by a set of decisions about how the system will handle quality attribute requirements. This is in contrast to an up-front architectural approach in which the architecture is defined primarily in a software architecture document.
An ADR makes architectural decisions transparent, helping the development team to clarify what it is doing and why, and to preserve that reasoning for people who will support and enhance the system in the future.
When software architecture evolves through a series of decisions, themselves born of hypotheses and experiments that test those hypotheses, development teams need a way to keep track of the architectural decisions they have made.
Over time, some of these decisions may change, and the development team needs a way to easily examine the decisions. Even when they don’t change, future teams will need to understand the choices and trade-offs that were considered so they can make better decisions about the evolution of the system.
What is the purpose of an ADR?
There isn’t a simple answer to this. Authors writing on the subject agree that it should document significant decisions, and some go further to say architecturally significant decisions. This sounds reasonable, but it’s hard to come to a consensus on what is significant, and even harder to decide what is architectural.
Many teams, lacking a place to record any sort of significant decision, put any decision they consider significant into an ADR, diluting the architectural aspects and turning an ADR into an “Any Decision Record”. Doing so overloads the ADR with lots of decisions that should not be there, and whose presence merely makes the really significant architectural decisions harder to see. So to decide what to put in an ADR, we have to decide what is architectural? And that’s not as easy as it might seem.
What is “architectural”?
Recently, we’ve caught ourselves in one of those “Princess Bride” moments in which Iñigo Montoya, played by Mandy Patinkin, says to Vizzini, played by Wallace Shawn, “You keep using that word … I do not think it means what you think it means.”
We throw around the word “architecture”, at least in the software context, and we act like we know exactly what it means. But when we more closely examine the concept, we have a hard time pinning down exactly what this means. In this article, we try to more clearly define what we think is architectural and what is not, and provide more explicit criteria for making the decision.
The term “architecture”, as it is applied to software development, is actually part of the problem; it’s the wrong metaphor. Architecture in the physical world is concerned largely with usability and aesthetics. What we call “software architecture” is more similar to structural engineering, which is mostly concerned with how a physical system resiliently handles loads.
Similarly, the art of “software architecture” is to anticipate the loads of a software system and to design for them. A key difference is that structural engineering is based on a vast body of knowledge based on thousands of years of experience, reinforced by scientifically derived laws of physics and mathematical models of those laws. Software is nothing like this. It is encoded thought, and there are few standard approaches to solving problems once we look beyond certain kinds of algorithms.
A good starting place for understanding software architecture is this observation by Grady Booch that contrasts architecture with design:
“All architecture is design, but not all design is architecture. Architecture represents the set of significant design decisions that shape the form and the function of a system, where significance is measured by cost of change.” (Grady Booch on Twitter).
The important parts of this observation are:
- Architectural decisions are costly to reverse, and
- Architectural decisions define the fundamental character or “shape” of the solution, which we interpret as the fundamental approach to solving the problems defined by the set of the system’s quality attribution requirements (QARs) – see Chapter 2 in “Software Architecture in Practice” for a deeper discussion.
If a decision does not involve both of these aspects, it’s our view that it should not be in the ADR. Let’s examine that assertion more closely.
What kinds of change are costly?
Some decisions are costly to change, but they aren’t necessarily complex, and by complex we mean “intellectually challenging” or “likely to really mess things up if you make the wrong decision.” In other words, rewriting code isn’t complex, it’s rethinking the concepts behind the code where real complexity arises.
To illustrate, some decisions are expensive to change but not very complex, such as:
- Redesigning the user interface for an application. Even when using a UI framework, changing visual metaphors can be time-consuming and expensive to modify, but it is rarely complex so long as the changes don’t affect the fundamental concepts the system deals with.
- Exchanging one major component or subsystem with another of equivalent functionality. An example of this is switching from one vendor’s SQL database to another vendor’s SQL database. These changes can take work, but conversion tools help, as does staying away from proprietary features. So long as the new component/subsystem supports the same fundamental concepts as the old one, the change doesn’t alter the architecture of the system.
- Changing programming languages may not even be architecturally significant so long as the languages support the same abstractions and programming language concepts. In other words, syntax changes aren’t architecturally significant, but changes to fundamental concepts or metaphors are.
With the appropriate conversion tools, these kinds of decisions might not actually be very costly. It used to be that rewriting a user interface was expensive but modern UI design tools and frameworks have made this sort of work relatively inexpensive. Deciding what UI framework, SQL database, or programming language to use is an implementation detail, not an architectural decision. Those are all significant decisions but they do not rise to the level of architectural decisions.
Even the cost criteria of “architecturally significant” boils down to “the shape of the solution”.
What does “the shape of the solution” mean?
The “shape of the solution”, for us, means the fundamental data structures and algorithms the system uses to solve its problem. Extending the observation about SQL databases above to provide an example, while the choice of a specific SQL database may not be architecturally significant, changing from using rows and columns to represent fundamental concepts to using tree structures or unstructured data is. The algorithms to search, sort, and update these different kinds of representations are very different, with different strengths and weaknesses, so the choice will dramatically affect the system’s ability to satisfy its QARs.
Speaking more generally, architectural decisions, for us, have the following characteristics:
- They involve the fundamental concepts the system uses, and its key abstractions as represented in the data structures (e.g. classes, types, …) it uses to share information across the entire system, and even between systems.
- They also involve the way these data structures are used, i.e. the fundamental algorithms that access and manipulate the data structures.
- Any change to data structures used to represent the fundamental concepts of the system affects the algorithms that use those data structures, and any changes to algorithms change the data structures that they use.
Architecture, then, establishes limits on the kinds of problems a system can solve, and even, sometimes, on the ability of developers to see different kinds of solutions by establishing a kind of hammer-nail blindness to alternatives. Changing architectural decisions means changing the fundamental concepts the system deals with, and the way that system works with those concepts.
In addition to algorithms and data structures that represent key concepts, other choices play critical roles in shaping the architecture, including, for example:
- Changes to the messaging paradigm – e.g. synchronous to asynchronous
- Changes to response time commitments – e.g. non-real-time to real-time
- Changes to concurrency/consistency strategies, e.g. optimistic versus pessimistic resource locking
- Changes to transaction control algorithms – e.g. fail/retry strategies
- Changes to data distribution that affect latency
- Changes to cache coherency strategies, especially for federated data
- Changes to security models, especially the granularity of security access when it extends to individual objects or elements.
Ultimately all of these choices turn into code that isn’t simple to change because the code implications of the choices are scattered throughout the software rather than being localized. If something can be localized and encapsulated, it’s typically not architectural because it can be changed without the impacts of the change rippling throughout the code.
Architecture and Decision Longevity
Sometimes the expected longevity of a decision causes a team to believe that a decision is architectural. Most decisions become long-term decisions because the funding model for most systems only considers the initial cost of development, not the long-term evolution of the system. When this is the case, every decision becomes a long-term decision. This does not make these decisions architectural, however; they need to have high cost and complexity to undo/redo in order for them to be architecturally significant.
To illustrate, a decision to select a database management system is usually regarded as architectural because many systems will use it for their lifetime, but if this decision is easily reversed without having to change code throughout the system, it’s generally not architecturally significant. Modern RDBMS technology is quite stable and relatively interchangeable between vendor products, so replacing a commercial product with an open-source product, and vice versa, is relatively easy so long as the interfaces with the database have been isolated. The architectural decision is the one to localize database dependencies and abstract vendor-specific interfaces, not the choice of the database itself.
Where decision longevity does come into play in an ADR is in addressing sustainability and resiliency. Sustainability involves a system being able to respond to an unknown set of future events of unknown probability and unknown impact. Resiliency is the ability of that system to resist failure when those events occur. What people mean when they say something is sustainable is that they believe that the system will be able to handle everything they can conceive of, and even things they cannot.
When do architectural decisions need to be made?
In the old days, a team would create a Software Architecture Document early in the development of a system, and that document would guide the development of the system throughout its lifecycle.
When a team uses an agile approach to the development of a system, the ADRs collectively take the place of the Software Architecture Document as they incrementally document the architecture to support the incremental development of the system. We have described in previous articles how a Minimum Viable Architecture (MVA) evolves in parallel with Minimum Viable Product (MVP) increments. In practical terms, this means that teams will make architectural decisions over time as they evolve the solution. Unlike the Software Architecture Document, the decisions documented by the ADRs are made up-front and all at once.
What’s the harm in using ADRs for all major decisions?
In brief, it muddies the waters, making the real architectural issues harder to see. Doing so makes discussions about fundamental decisions harder because the issue isn’t clear, especially if the implications of a decision are not fully spelled out.
Teams still have a need to record non-architectural decisions for a variety of reasons, many of which boil down to having a record of what was decided and why, in case someone needs to explain or justify it later. Sometimes a decision is classified as “architectural” in order to record it in an ADR, for the lack of a better place to record it.
Things that ADRs should not be used for:
- Promoting reuse. Some people, managers especially, see the ADRs as a means to enforce reusability. They want to see common components and subsystems reused because they think this will lower cost and simplify development. This is only true, however, if the reused components and subsystems are fit for purpose and result in a better solution.
When they are not, they convolute designs and make the architecture worse. We’ve all probably had the experience of struggling to make the “company standard” work when it’s not the best solution for the problem at hand. The result is usually increased cost and reduced resilience.
Promoting reusability is one aspect of knowledge sharing across a development organization, but there are better ways to promote this knowledge sharing than gumming up the ADRs with lots of information about the potential reuse of designs and code. We think it is better to make an ADR a clear record of the problem the team is trying to solve and their reasons for choosing their approach.
- Responsibility deflection (CYA). Some teams believe that by putting a decision in an ADR they can absolve themselves from the consequences of that decision should it prove wrong. The more people who see and explicitly or tacitly approve an ADR, the more the responsibility for making a poor decision is diluted. There is, they think, safety in numbers.
Punishing people for bad decisions is a sign of a toxic management culture. Development teams make the best decisions they can with the information available to them at the time. When they learn more, often through building and deploying the system, some of these decisions will change. The key to reducing the cost of decisions that change is to build the system in small increments and to frequently test hypotheses. Criticizing past decisions is demoralizing and unproductive. If teams don’t have to fear being blamed for their decisions, they can focus on building better solutions by experimenting instead of using an ADR to inoculate themselves from blame.
- Recording non-architectural product decisions. This often happens because teams make important decisions all the time, but if they lack a place to record them they are going to put them in an ADR. Misusing the ADRs in this way makes the architecture harder to perceive: if every decision is architectural, no decision is architectural. Put another way, an ADR that turns into the “Any Decision Record” has lost its purpose.
There is an easy remedy to this: simply keep a log of important decisions that are not related to architecture. Architectural decisions are usually only understandable to developers, while records of most other important decisions have a much wider audience. Keeping them separate often makes everyone happier.
Even though all architectural decisions are important, not every important decision is architectural. Creating separate records of architectural decisions and other important decisions helps to improve communication across organizations. ADRs contain technical discussions that are not usually of broad interest, and keeping them separate makes the architecture of a system easier to understand.
ADRs, if kept focused on architecture, provide an understanding of the evolution of a team’s thought processes as they balance choices and trade-offs. Decisions are never wrong, they are just an indication of the team’s thinking at a point in time. Yes, they may choose different approaches as they learn more, over time, but having a record of this evolution is useful to preserve. Seeing how the team’s thinking has evolved provides insights into current and future trade-offs.
In software architecture, there are often no perfect solutions, only “less than perfect” alternatives that need to be balanced. Being able to see these choices more clearly helps current and future teams better understand the trade-offs they may have to make.