Over the past little while, I have increasingly found myself having the responsibility in making and advising on technology architectural decisions for various organisations. When working on small and simple use-cases, these decisions are normally about the technology stack of a single application and the structure of that application. But when working within larger enterprises, complexity changes the scope of the decision making from one application to multiple systems interacting with each other.
I started seeing the hype of the book Building Evolutionary Architectures last year from the Twitter accounts of employees of my former employer, ThoughtWorks. I have a high regard for the authors Rebecca Parsons, Neal Ford and Patrick Kua. Martin Fowler also published a candid foreword he wrote for the book on his blog inciting me to by an electronic copy a few weeks ago.
Overall, I thought the book to be a great and insightful read. It added mental categories to concepts and ideas experience had baked into my intuition without a theoretical underpinning.
The book starts off by defining what architecture is and what it should be to allow for guided incremental change, delving into the tensions of long-term planning, and allowing for team autonomy and responsive change.
From here, there is a discussion on Fitness Functions, a term borrowed from evolutionary computing, which describe functions used to test whether solutions that emerge from generated software are closer to the intended goal. The authors talk about defining what the context-specific goal of the architecture is, and creating tests, both automated and manual, to continually test whether the evolving architecture is aligned to these intended goals. Honestly, though I understand it theoretically, this concept confused me the most as I haven’t seen much of the implementation in practice.
Engineering practices that support evolutionary architecture were discussed, with descriptions of continuous integration and continuous delivery pipelines explored, as well as the integration of the fore-mentioned fitness functions into these pipelines.
The most interesting and insightful section for me came next, where different architectural patterns were discussed and evaluated according to three criteria: incremental change, the enabling of guided change with fitness functions, and the presence of appropriate coupling. The patterns analysed were monoliths (including its various sub-patterns), event-driven architectures, service oriented architectures, and service based architectures. While I had experience with these various architectural forms, here they were simply described with the trade-offs of each succinctly explained.
Evolutionary data was explored next, with a discussion on allowing for and managing changing data structures. Pitfalls and anti-patterns of evolutionary architectures were discussed, and lastly everything was brought together with great practical tips on the pragmatic implementation of evolutionary architectures within different contexts.
A great insight overall was the contextual nature of architectural choices, for example how highly transactional contexts aren’t suitable to microservices. Or how event-based architectures make it difficult to track the flow of a transaction (where there is no mediator present). Also, I found the categorisation of the various architectural types useful, especially in understanding their tradeoffs.
This was an insightful technical read, with my only criticism being that the authors could have said more. At the end of it, a part of me felt the book finished too quickly with some half-baked concepts requiring further investigation (which was probably the intention of the authors).