When agile development methodology is focussed upon, what is usually seen is that how swiftly teams can release new code. But a quicker pace of release will only matter if it simplifies the process of seeing how alterations will affect business objectives, application performance and user experience. Or, when test-driven development is focussed upon, it is seen as a perfect practice for feature development in order to enhance code quality and test coverage. But you also need to take care of non-functional requirements like scalability, reliability, observability and other architectural concerns. There is a perpetual evolution of architecture standards happening and Building Evolutionary Architecture (co-authored by Neal Ford, Rebecca Parsons and Patrick Kua) describes that the very notion of architecture supporting change as evolutionary architecture.
Evolutionary architecture streamlines the process of making huge changes to domains like databases, business processes, service interfaces or user interface without hampering other domains. Thoughtworks states that incremental, guided alteration is supported by evolutionary architecture as a first principle across multiple dimensions. The idea of evolutionary architecture comes from evolutionary computing in which developers define algorithms that will, in turn, achieve a result. Determining whether or not an algorithm is good or not than the one that was last implemented can be done using fitness functions. Building evolutionary architecture states that “every system at different points of their life need to optimise to be fit for its environment.” It further adds that “evolutionary architectures make it explicit what fit means with as much automation as possible.”
Identification of tests as fitness functions may be protracting the evolutionary metaphor a bit and you are not leveraging fitness functions to select different candidate solutions in a simulation of natural selection. The application of fitness functions doesn’t have much to do with incrementally rejecting solutions but is useful for using metrics in order to guide future development effort. It can also be troublesome to come up with objective and repeatable tests for measuring the right things and a plethora of investments in genetic programming is done on fitness functions to ensure the correlation with design goals. Measurement of wrong things can lead to an inappropriate solution.
You may want to define architectural fitness functions that emphasise on the structure of code and test frameworks can also be helpful for building clear, atomic tests that can be integrated into the build process. Static analysis tools like SonarQube can be useful for measuring intricacy of code. ArchUnit can help in enforcing code structure rules as JUnit test assertions so that they can be integrated into a continuous integration pipeline. But this approach is limited as it only takes into consideration a very narrow perspective of the system as an entire picture of architectural health must take multiple dimensions into account beyond the technical implementation. Moreover, architectural fitness cannot be measured by merely observing the code or design.
No matter what the application architecture is (it can be monolith, microservices, or hybrid microservices), fitness function-driven development can be of tremendous help when it comes introducing continuous feedback for architectural adherence and let the development process know as it happens.
Atomic fitness function can be done by a unit test that looks at a single characteristic of one deployable artefact in order to check how it works. And as consumer-driven contracts are commonplace in this microservices era for testing integration with other services, it is getting easier to make alteration in one service that does not cause a problem to the integration with others. To see how the architecture pans out as a whole, holistic fitness function can be useful.
Using fitness functions in evolutionary architecture communicate architectural standards as code and assists in the empowerment of development teams for delivering features that align well with architectural objectives.