The architecture of a system is vitally important to it's success. A good architecture ensures that the system meets functional and non-functional requirements. However, agile organisations also depend on their systems to change as they do. As the requirements on the system change so must the architecture in order to satisfy them.
Historically, the architecture of a system was designed up-front using whatever crystal ball was available to the architect at the time. While more recently, current agile processes can lead to architectural decisions being made adhoc, leading to the infamous "big ball of mud".
Building an evolutionary architecture attempts to satisfy all the requirements. Ensuring that the architecture of the system is easy to change at any point in time.
What does an evolutionary architecture look like?
The whole point of an architecture is that it's context specific. It fits the requirements and constraints placed upon it by the users of the system at that point in time. We can start, instead, by looking at the opposite of an evolutionary architecture.
The "big ball of mud" is at one extreme of the spectrum. In the big ball of mud a change in functionality necessitates the changing of code throughout the entire application. This brings with it an increased risk of regression in parts of the system thought unrelated. At this point, changing the architecture is nearly impossible.
Slightly further along the spectrum we find systems where there's definitely been thought applied to maintain the architecture but whilst there is consistency in the code there's little chance of changing it easily.
Herein lies the first, somewhat obvious, point about an evolutionary architecture: it should be easy to change!
Being easy to change gives rise to the ability to experiment with an architecture easily as well.
There is, of course, another path to enabling experimentation: a very good test suite. However, enabling experimentation through a large regression test suite simply gives us the confidence to experiment, but without the speed.
So how do we make an architecture that is easy to change?
For this, we can look to Domain Driven Design (DDD) to give us some insight. The patterns in DDD enable us to build systems that are easy to change. Indeed, if we do it correctly, we start off with a simple architecture that is easy to re-factor in to a more complex architecture when the need arises. Better still, we can re-factor only the parts of the application that require the increased complexity.
The patterns and building blocks of DDD all enforce the same principle: that of low coupling and high cohesion.
In the strategic patterns we find the concept of bounded contexts. A bounded context ideally maps to a single domain and is governed by it's own ubiquitous language. By ensuring low coupling between bounded contexts we can easily re-architect a single bounded context without interference from others.
In order to achieve this low coupling DDD also discusses patterns for integrating with other bounded contexts and other systems. The use of anti-corruption layers between systems can ensure that the domains stay separated and that concepts are translated appropriately.
The building blocks of DDD go a level lower, in to the code itself that makes up the systems. The key building blocks for building an evolutional architecture are as follows.
A clean separation of domain, infrastructure, UI and application enables evolution of the individual parts independently of the others. The prime motivation for this in DDD is to allow modelling of the domain without any constraints placed upon it from the infrastructure or architecture.
This separation also enables another key feature of an evolutionary architecture: the ability to delay decisions about architecture until absolutely necessary
By using interfaces in the domain layer you can delay decisions about the implementation of the infrastructure layer until you are happy with the functionality of the domain layer.
By grouping up parts of the bounded context in to modules we can further maximise the high cohesion inside them and the low coupling between them. This is obviously similar to the bounded context concept but the domain is the same between the modules of a bounded context (ideally).
Domain events allow different modules and different systems to respond to changes. By using a pub-sub mechanism you can ensure that producers of events are de-coupled from the consumers of the events.
Given the concepts taken from DDD we can see how we can obey KISS (keep is simple, stupid) to start off with a simple architecture but be able to grow the architecture as required.
The image above shows how you can start with something simple: a single application with a single database and a in-process events system. You can then re-architect pieces of the application to use different systems (and out-of-process messaging) all the way to breaking down a bounded context in to a micro services based architecture. By obeying the patterns of DDD we are able to independently architect the various parts of the system based on their requirements.
Preventing evolutionionary stagnation
It takes great discipline to maintain an evolutionary architecture. There are, however, testing frameworks available which will allow you to test the architecture itself. For example, in .Net, you can make use of the NetArchTest framework to ensure that your architecture stays consistent between re-factors.
// Classes in the presentation should not directly reference repositories var result = Types.InCurrentDomain() .That() .ResideInNamespace("NetArchTest.SampleLibrary.Presentation") .ShouldNot() .HaveDependencyOn("NetArchTest.SampleLibrary.Data") .GetResult() .IsSuccessful;