The microservice architectural style is taking the world by storm. Last March, O'Reilly hosted their first Software Architecture Conference, and a huge percentage of the abstracts the program committee received touched on some aspect of microservices. Why is this architectural style suddenly all the rage?
Microservices are the first post DevOps revolution architectural style, the first to fully embrace the engineering practices of Continuous Delivery. It is also an example of an evolutionary architecture, which supports incremental non-breaking change as a first principle along multiple dimensions at the structural level of the application. However, it is only one of group of architectures that support certain evolutionary behaviors. This article investigates some characteristics and principles of this family of architectural style.
Common wisdom in software once held that architectural elements are "difficult to change later." An evolutionary architecture designs for incremental change in an architecture as a first principle. Evolutionary architectures are appealing because change has historically been difficult to anticipate and expensive to retrofit. If evolutionary change is built into the architecture, change becomes easier and cheaper, allowing changes to development practices, release practices, and overall agility.
Microservices meet this definition because of its strong bounded context principle, making the logical division described in Evan's Domain Driven Design a physical separation. Microservices achieve this separation via advanced DevOps practices like machine provisioning, testing, and automated deployments. Because each service is decoupled from all other services (at the structural level), replacing one microservice with another resembles swapping one Lego brick for another.
Characteristics of Evolutionary Architectures
Evolutionary architectures exhibit several common characteristics. We have identified a large number for the upcoming Evolutionary Architecture book; here are a few of them.
Modularity and Coupling
The ability to separate components along well defined boundaries has obvious benefit if a developer wants to make a non-breaking change. The absence of any architectural elements, the legendary Big Ball of Mud architecture, doesn’t support evolution because it lacks compartmentalization.
[Coupling between classes (the points on the perimeter) in a Big Ball of Mud from an unnamed client project.]
Inappropriate coupling inhibits evolution by propagating changes in difficult to predict ways. Evolutionary architectures all support some level of modularity, typically at the technical architecture (for example, the classic layered architecture).
Organized Around Business Capabilities
Increasingly, modern successful architectures feature modularity at the domain architecture level as well, inspired by Domain Driven Design. Service-based architectures differ from traditional SOA primarily in the partitioning strategy: SOA is strictly partitioned along technical layers, while service-based architectures tend towards the microservice's inspired domain partitioning.
Experimentation is one of the superpowers evolutionary architectures deliver to the business. Operationally inexpensive trivial change to applications allows common Continuous Delivery practices like A/B testing, Canary Releases, and others. Often, microservice architectures are designed around routing between services to define applications, allowing several versions of a particular service to exist within the ecosystem. This in turn allows experimentation and gradual replacement of existing functionality. Ultimately, this power enables your business to spend less time speculating about story backlogs and instead engage in hypothesis driven development.
Principles of Evolutionary Architectures
One way of thinking about evolutionary architectures is through principles. The principles describe various characteristics of either architectures themselves or methods for designing architectures. Some of the principles focus attention on when particular architectural decisions are made in the process.
We distinguish between emergent and evolutionary architecture, and this distinction is an important one. Much like in evolutionary computation techniques like genetic algorithms, an architectural fitness function specifies what our target architecture looks like. Some systems need high uptime while others are more concerned with throughput or security.
[A radar chart used to highlight important fitness functions appropriate to this software system.]
The up-front thinking about what the fitness function should be for a particular system provides the guidance for decision making and the timing of decisions. Architectural decisions are scored relative to the fitness function so that we can see that the architecture is evolving in the right direction.
Bring the Pain Forward
Many of the practices in continuous delivery and evolutionary architecture exemplify the bring the pain forward principle, as inspired by the eXtreme Programming community. When something on a project has the potential to cause pain, force yourself to do it more often and earlier, which in turn encourages you to automate the pain away and identify issues early. Common continuous delivery practices like deployment pipelines, automated machine provisioning, and database migrations make evolutionary architecture easier by removing common pain points for change.
Last Responsible Moment
When decisions occur is a major distinction between traditional architecture and evolutionary architecture. These decisions could be around the structure of the application, the technology stack, specific tools, or communication patterns. In a traditional architecture, these decisions manifest themselves early, before writing code. In an evolutionary architecture, we wait for the last responsible moment to make decisions. The benefit of delaying a decision is the additional information available to make the decision. The cost is any potential re-work that has to occur once a decision has been made, which can be mitigated through appropriate abstractions—but the cost is still real. However, the cost of making a decision too early is also real. Consider the choice of a messaging tool. Various tools have different supported features. If we choose a heavier-weight tool than we ultimately need, we have introduced a source of technical debt into our project. This debt comes in the form of the drag on development caused by using the wrong tool. This isn’t an excuse to preemptively “abstract all the things”—we still support the agile YAGNI (You Ain’t Gonna Need It) principle—but rather an informed attempt to make a decision at the proper time.
Of course, the immediate question when thinking about the last responsible moment is deciding when that moment is. The fitness function provides the guidance for that decision. Decisions that will have significant influence on other architectural or design choices, or decisions that impact a critical success factor for the project should be made earlier. The impact to the project of delaying such a decision is often greater than the benefit of waiting.
Software architects have the responsibility to elucidate decisions about how systems fit together, often by creating diagrams. Too many architects fail to realize that a static, two dimensional picture of architecture has a short shelf life. The software universe exists in a state of constant flux; it is dynamic rather than static. Architecture isn't an equation but rather a snapshot of an ongoing process.
Continuous Delivery and the DevOps movement illustrated the pitfalls of ignoring the effort required to implement an architecture and keep it current. There is nothing wrong with modeling architecture and capturing those efforts, but the implementation is only the first step. Architecture is abstract until operationalized. In other words, you can't really judge the long-term viability of any architecture until you've not only implemented it but also upgraded it. And perhaps even enabled it to withstand unusual occurrences.
Operational awareness by the architect is critical to evolutionary architectures. Evolution impacts the specifics of the implementation, thus the implementation details cannot be ignored. The requirements that continuous delivery imposes upon architecture makes implementation more visible and eases its evolution. Therefore, continuous delivery is an important enabler for any evolutionary architecture.
In this episode of the Thoughtworks Tech Leaders Podcast, Rebecca and Neal discuss the meaning of evolutionary architecture and how organizations can use it as a business advantage.
Watch a recent webinar on Evolutionary Architecture presented by Neal.