In the context of this post, Fitness Functions are objective functions, used to summarize how close a prospective evolutionary design solution is to achieving a set of aims or goals.
In evolutionary computing, the fitness function determines whether an algorithm has improved over time. In other words, as each invariant 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 when creating evolutionary architectures. As they evolve, we need mechanisms to evaluate how changes impact the important characteristics of the architecture and prevent degradation of those characteristics over time.
When you define an architectural characteristic, you want to protect that as your architecture evolves. And you can use fitness functions to achieve just that.
To build evolvable software systems, architects must think beyond just the technical architecture. Each project has different dimensions that architects must take into consideration, especially when thinking about the evolution of a given architecture.
Here are some common dimensions that affect evolvability in modern software architectures:
- Technical: The implementation parts of the architecture. The frameworks, dependent libraries, implementation languages, etc.
- Data: Database schemas, table layouts, optimization planning, etc.
- Security: Security policies, guidelines, etc.
Each of these perspectives forms a dimension of the architecture -an intentional partitioning of the parts supporting a particular perspective.
Architectural Fitness Functions
An architectural fitness function provides an objective integrity assessment of some architectural characteristic(s) (or dimension)
-Building evolutionary architectures
We use fitness functions to protect the various architectural characteristics or dimensions defined for a given project. These will greatly differ across systems and organizations; based on business drivers, technical capabilities, and a host of other factors.
So, What is a fitness function?
Purely mathematically speaking, a 'function' is something that takes input from some allowed set of input values and produces an output in some allowed set of output values. In software, we also generally use the term 'function' to refer to something that is actually implementable.
However, as with acceptance criteria in agile software methodologies, fitness functions may not always be implementable in software (e.g., a required manual process for regulatory reasons). If required, architects must define manual fitness functions to help guide the evolution of the system. But, automated checks are preferable off course, and automation within the bounds of Continuous Integration are always the best option.
Performance requirements make good use of fitness functions and are a good example. Consider a requirement that all service calls must respond within 100ms. We can implement a test that measures the response time, and fails if the result is greater than 100ms.
Fitness functions can also be used to maintain coding standards. A common coding code metric is cyclomatic complexity, a measure of function or method complexity. An architect may set thresholds for upper values, guarded by unit tests running in continuous integration.
We say an architecture is guided by a fitness function if we use it to evaluate an individual architectural choice, characteristic or dimension; and are able to determine the impact of a system-wide change.
Atomic vs Holistic
Atomic fitness functions run against a singular context and exercise one particular aspect of the architecture.
An excellent example is a unit test that verifies some architectural characteristic, such as modular coupling or cyclomatic complexity.
Holistic fitness functions run against a shared context and exercise a combination of architectural aspects such as security and scalability.
Triggered vs Continual
Triggered fitness functions run based on a particular event, such as a developer executing a unit test, a deployment pipeline running unit tests, or a QA person performing exploratory testing.
Continual tests don't run on a schedule, but instead execute constant verification of architectural aspect(s) such as transaction speed. Monitoring Driven Development (MDD) is a testing technique gaining popularity. Rather than relying solely on tests to verify system results, MDD uses monitors in production to asses both technical and business health. These continual fitness functions are more dynamic than standard triggered tests.
Static vs Dynamic
Static fitness functions have a fixed result, such as the binary pass/fail of a unit test.
Dynamic fitness functions rely on a shifting definition based on extra context. For example, a company might build a sliding value for performance based on scalability. More scale means slower performance is permitted, but only within a range.
Automated vs Manual
Clearly, developers and architects like things automated. Automated fitness functions will execute within an automated context, while manual fitness functions, like legal requirements, defy automation. Manual fitness functions are cases that require the verification of person-based process.
While most fitness functions trigger on change, architects may want to build a time component into assessing fitness. For example, if a project uses an encryption library, the architect may want to create a temporal fitness function as a reminder to check if important updates have been performed.
Intentional over Emergent
Architects never know all important parts of an architecture at the beginning and thus must identify fitness functions as the system evolves. These emerge during the actual development of the system.
Some architectures have specific concerns, such as special security or regulatory requirements. These are related to the specific nature of the organization -or the domain- and the specific project.
Conclusion and credits
We live in a world where software technologies and systems are ever changing. And we want to build architecture that are able to evolve with them.
And more specifically, we want our architectures to evolve in a guided way. Fitness Functions are a good way to place constraints on different aspects of the architecture to guide them in certain evolutionary directions.
A good example is dog breeding. By selecting the characteristics we want, we can create a vast number of different shaped canines in a relatively short amount of time.
Much credit for this blogpost goes to the book 'Building Evolutionary Architectures' by Neal Ford, Rebecca Parsons & Patrick Kua. Much of the ideas presented here are documented in their book. I wrote this blogpost as a reminder for myself, to inform you -the reader-, and hopefully to motivate you to read the book; as they explain it more thoroughly than I ever could.