In a previous post, I’ve underlined the philosophy behind Domain Driven Design, DDD, and now I’d like to move to a practical approach that handles real issues in software development and architecture: requirements that constantly change, and models that are never precise, never current, and/or never using the best technology available. One of the solution to such problems is to build an evolutionary architecture.
To be able to have a discussion we have to understand the part that software architecture plays, which is not straight forward considering the many definitions and re-definitions of it. I’ll note the particularly fascinating ones.
The architecture of a software system (at a given point in time) is its organization or structure of significant components interacting through interfaces, those components being composed of successively smaller components and interfaces. — IEEE definition of software architecture
In most successful software projects, the expert developers working on that project have a shared understanding of the design. This shared understanding is called “architecture.” This understanding includes how the system is divided into components and how the components interact through interfaces. — Ralph Johnson
Going towards more abstract definitions such as the following.
Architecture is the stuff that’s hard to change later. And there should be as little of that stuff as possible. — Martin Fowler
Architecture is about the important stuff. Whatever that is. — Martin Fowler
Stuff that’s hard to change later. — Neal Ford
These definitions barely overlap but there’s still a vague essence
joining them which we can extract. We can say that architecture is
concerned with the important decisions in a software project, the
objects of those decisions, the shared knowledge of them, and how to
reason about them. If we view this from an evolutionary architecture
standpoint, the best architecture is one where decisions are flexible,
easily replaceable, reversible, and deferred as late as possible so that
they can be substituted for alternatives that recent experiences have
shown to be superior.
Because architecture is about decision-making, it is inherently tied with the concept of technical debt, the compromise of trading time for a design that is not perfect. Keep in mind that debt accumulates and often leads to architectural decay as changes keep coming and entropy increases.
Similarly, due to the vague definition of architecture, the role of architect is hard to describe. Whether it should be a completely separate role, or whether everyone in a team acts as one, is ambiguous. The vociferous software architecture evangelist Martin Fowler prefers the term Architectus Oryzus, referring to architects that are also active contributors on the projects, thus getting direct insights from their involvement.
The software architecture thought process can be applied at two broad levels: the application level and the enterprise level. Application architecture is about describing the structure of the application and how they fit together, usually using design patterns, while enterprise architecture is about the organizational level software issues such as practices, information flow, methodology standards, release mechanisms, personnel related activities, technology stacks enforced, etc.
Design relates to all the well known development design patterns, refactoring techniques, the usage of frameworks, how to bundle components together, and other daily concerns. In an evolutionary architecture, it’s preferable to have an emergent design instead of one that is set up front.
This gives us a good idea of what software architecture is about, so what’s the current state of it, and why do we need a solution such as building evolutionary architectures?
The usual way we develop software today is by fighting incoming changes
that we want to incorporate in the current architecture. Software
development has a dynamic equilibrium, and currently we find that
software is in a constantly unbalanced and unstable state whenever
there are changes to be included. That is because even though we’d like
to do the right things at the right time, we can’t predict what those
decisions should be, predictability is almost impossible. For example,
we can’t predict disruptive technologies that don’t exist yet. As the
software ages, we juggle changes and new requirements, there’s no room
for experimentation, we only respond.
Stakeholders want the software to fulfill architecture significant requirements, also known as the “ilities” and KPI, such as auditability, performance, security, scalability, privacy, legality, productivity, portability, stability, etc. They expect those to not degrade. Hence, we have to find the least-worst trade-off between them and blindly not introduce anything that could hinder them. This is hard to do, be it because of a business-driven change, such as new features, new customers, new market, etc., or be it because of ecosystem change such as advances in technology, library upgrades, frameworks, operating systems, etc.
In recent years, we’ve seen the rise of agile development methodologies that are meant to replace the waterfall approach. They are more apt at facing this challenge, they create an iterative and dynamic way to control the change process. What we call evolutionary architecture starts from the idea of embracing change and constant feedback but wants to apply it across the whole architecture spectrum, on multiple dimensions. It’s not the strongest that survive, it’s the ones that are the most responsive to change. What is evolutionary software architecture.
Evolutionary architecture is a meta-architecture, a way of thinking about software in evolutionary terms. A guide, a first derivative, dictating design principles that promote change as a first citizen. Here is Neal Ford’s, Rebecca Parson’s, and Patrick Kua’s definition in their book “Building Evolutionary Architectures” which we’ll dissect.
An evolutionary architecture supports guided, incremental change across multiple dimensions.
- Multiple 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
While the agile methodology is only concerned with people and processes,
evolutionary architecture encompasses the whole spectrum including
the technical, the data, the domain, the security, the organizational,
and the operational aspects. We want different perspectives, and all
evolvable, those are our dimensions. The evolutionary mindset should
surround it all in a holistic view of software systems. For this,
we add a new requirement, an “-ility”, we call the evolvability of a
dimension. This will help measure how easily change in a dimension can
evolve the architecture — easily be included in the dynamic equilibrium.
For example, the big ball of mud architecture, with its extreme coupling and architectural rotting, has a dimension of evolvability of 0 because any change in any dimension is daunting.
The layered architecture has a one-dimensional structural evolvability because change at one layer ripples only through the lower one. However, the domain dimension evolvability is often 0 when domain concepts are smeared and coupled across layer boundaries, thus a domain change requires major refactoring and ripples through all the layers.
The microservice style of architecture, that hinges on the post-devops and agile revolution, has a structural and domain dimension evolvability of
n being the number of isolated services running. Each service in
a microservice architecture represents a domain bounded context, which
can be changed independently of the others because of its boundary. In
the world of evolutionary architecture we call such disjunct piece a
quantum. An architectural quantum is an independently deployable component
with high functional cohesion, which includes all the structural elements
required for the system to function properly. In a monolith architecture,
the whole monolith is the quantum. However, from a temporal coupling
perspective dimension, transaction may resonate through multiple services
in a microservice architecture, and thus have an evolvability in the
transactional dimension of 0.
- Incremental change
It is not enough to have a measure of how easy change can be applied,
we also need to continually and incrementally do it. This applies both
to how teams build software, such as the agile methodology, and how the
software is deployed, things such as continuous integration, continuous
delivery, and continuous verification/validation.
These rely on good devops practices that let you take back control in complex systems, such as automated deployment pipelines, automated machine provisioning, good monitoring, gradual migration of new services by controlling routes, using database migration tools, using chaos engineering to facilitate the management of services, and more.
- Guided Change
We can experiment without hassle, trivially and reversibly, with evolvability across multiple dimension and incremental change. But to start the evolutionary process this is what we need: a guide that will push, using experiments as the main stressors, the architecture in the direction we want. We call this selector an evolutionary fitness function, similar to the language used in genetic algorithms for the optimization function.
An Architectural fitness function provides an objective integrity assessment of some architectural characteristic(s).
Fitness functions are metrics that can cover one or multiple dimensions we
care about and want to optimize. There’s a wide range of such functions,
and this is where the evolutionary architecture shines, it encourages
testing, hypothesis, and gathering data in all manner possible to see
how these metrics evolve, and the software along with it. Experimentation
and hypothesis-driven development are some superpowers that evolutionary
This isn’t limited to the usual test units and static analysis but extends way beyond simple code quality metrics. These could be automated or not, global or not, continuous or not, dynamic or not, domain specific or not, etc. Let’s mention interesting techniques that can be used for experimentation and that are now facilitated.
- A/B testing.
- Canary Releases aka phased rollout.
- TDD to find emergent design.
- Security as code, especially in the deployment pipeline
- Architecture as code, also in the deployment pipeline with test framework such as ArchUnit.
- Licenses as code, surprisingly this works too.
- Test in production: through instrumentation and metrics, or direct interaction with users.
- Feature flags/feature toggles, to toggle behavior on and off.
- Chaos engineering, for example using the simian army as a continuous fitness function. “The facilitation of experiments to uncover systemic weakness”.
- Social code analysis to find hotspot in code.
- Github scientist, to test hypothesis in production while keeping
- Decides whether to run or not the try block.
- Randomizes the order in which use and try blocks are run.
- Measures the durations of all behaviors.
- Compares the result of try to the result of use.
- Swallows (but records) any exceptions raised in the try.
- Publishes all the information.
The benefit of all the experimentation are soon seen, creating a real interactive feedback loop with users, a buffet of options. The dynamic equilibrium takes care of itself and there are fewer surprises.
This is enabled by the team building the evolutionary architecture. Like with DDD, Conway’s law applies, the shape of the organization is directly reflected in the software — You can’t affect the architecture without affecting the people that build it.
So far, we’ve seen that such team should embrace devops and agile
development, that’s a given. Additionally, the team should itself be a
cocoon for evolution and experimentation. By making it cross-functional,
that is every role and expertise should be found in it, and responsible
for a single project, we remove the bottlenecks in the organization. We
need a team that resembles our architectural quantum.
A small team, one that can be fed by two pizzas — a two-pizzas team — avoids the separation between who decides what needs to be done and who decides how it’s going to be done. Everyone is there and decides together. We talk of teams in charge of products rather than projects.
The size of the team also allows information to flow seamlessly. All can share the architectural and domain knowledge. Methods that can be used are the usual documentation, architectural decision records, pair programming, and even mob programming.
As nice as it is to have teams that are single working units taking the
best decisions for their projects, it’s also important to limit their
boundaries. Many companies prefer giving loose recommendations about
the software stacks teams can use instead of letting them have their
own silos of specialized and centralized knowledge. Again, we face
the dynamic equilibrium but this time at the team level. The parallel
in enterprise architecture is called the “classic alternatives”
Human governance in these teams shouldn’t be restrictive because it would make it hard to move. However, the teams are guided by their own fitness functions, an automatic architectural governance. The continuous verifications in the delivery pipeline act as the guard-rail mechanism.
There are two big principles that should be kept in mind while applying all the above: “last responsible moment” and “bring the pain forward”. Together, they have the effect of making team members less hesitant and more prone to experiment.
The last responsible moment, an idea from the LEAN
is about postponing decisions that are not immediately required to find
the time to gather as much information as possible to let the best
possible choice emerge.
This is especially useful when it comes to structural designs and technological decisions, as insights and clear contexts appear late. It helps avoid the potential cost and technical debts of useless abstractions and vendor-locking the code to frameworks. That is in direct opposition to the classical way of doing software architecture where those decisions are taken upfront.
Bringing the pain forward, an idea inspired from the extreme programming
methodology, is about facing difficult, long, painful tasks instead of
postponing them. The more often we encounter them, the more we’ll know
their ins-and-outs, and the more we’ll be incited to automate the pain
away. It’s the dynamic equilibrium of pain vs time, the pain increases
exponentially if we wait.
This is why it’s encouraged to do things like test in production, apply techniques of chaos engineering, rebooting often, garbage collecting services, merging code often, using database migration tools, etc. Eventually, the known-unknowns become known-knowns, the common predictable pain is gone.
In a world where software keeps getting complex, building evolutionary architectures leads into the topic of building robust, resilient, adaptive, and rugged systems. Software is now an intimate part of our lives, and we rely on it, it has real world effects. We could get inspired by the aerospace world and take a look at the checklist manifesto, or we could embrace statelessness and a throwable/disposable architecture (disposable software, erase your darlings), or maybe go the way of the flexible reactive architectures with their self-healing mechanisms. Anything is possible.
In this post, I’ve given my overview of the way I perceive evolutionary software architecture and its place in software architecture as a whole. It is clearly a step forward from the typical static view of architecture and offers a novel and organic approach, as the name implies. None of what is described is necessarily novel but putting all these methods and thinking together is. If you want an in-depth explanation, you can take a look at the O’Reilly book “Building Evolutionary Software” by Neal Ford, Rebecca Parsons, and Patrick Kua. I hope this article kick-starts your journey.
- The slides
- The book from O’Reilly
If you want to have a more in depth discussion I'm always available by email or irc.
We can discuss and argue about what you like and dislike, about new ideas to consider, opinions, etc..
If you don't feel like "having a discussion" or are intimidated by emails then you can simply say something small in the comment sections below and/or share it with your friends.