Designing evolutionary architectures
Practice · Chapter 16
What an evolutionary architecture is
Section titled “What an evolutionary architecture is”- Architecture consists of the earliest, hardest-to-change design decisions (see What is software architecture?). Evolutionary architecture aims to make even those decisions easy to change, plus support non-architectural modifications.
- Parsons, Ford, and Kua (Building Evolutionary Architectures) define it as one that supports “guided incremental change across multiple dimensions” — three ideas worth unpacking: guided, incremental, and multiple dimensions.
Making guided architectural changes
Section titled “Making guided architectural changes”- Changes must be guided so the architecture’s characteristics and quality attributes remain intact.
- Systems are dynamic; ad-hoc changes can have unintended consequences and, without oversight, quality erodes and the architecture stops meeting its purpose.
- The architect’s job is to steer changes so requirements and architectural goals keep being met. Fitness functions are one tool for measuring the impact of a change.
Fitness functions
Section titled “Fitness functions”- Borrowed from evolutionary computing, where a fitness function is an objective function measuring how close a solution is to the desired result (used in genetic algorithms).
- Applied to architecture, they objectively assess how close the design is to the desired architectural characteristics.
- They should be clearly defined and quantitative, so you can compare the architecture before vs. after a change, and compare competing solutions.
Categories of fitness function (not mutually exclusive)
Section titled “Categories of fitness function (not mutually exclusive)”| Category | Meaning |
|---|---|
| Atomic vs. holistic | Atomic tests one characteristic in one context (e.g. a unit test); holistic tests several characteristics together. Both matter because features that pass in isolation may fail in combination. |
| Triggered vs. continuous | Triggered runs on an event (build, unit test); continuous runs constantly (e.g. a monitoring alert). |
| Static vs. dynamic | Static checks against a constant value; dynamic acceptable values shift with context (e.g. lower performance tolerated at higher scale). |
| Automated vs. manual | Automated (in CI/build) is ideal when possible; some functions must or should be run manually. |
| Temporal | Triggered by elapsed time rather than a system change (e.g. apply a framework patch within N days of release). |
| Intentional vs. emergent | Intentional are defined early once characteristics are known; emergent appear as the system develops. |
| Domain-specific | Based on business-domain concerns — regulatory, security, PII — ensuring the architecture keeps conforming. |
Examples
Section titled “Examples”- Fitness functions can be tests (automated or manual), monitoring, or metric collection. Not every test is a fitness function — only those that assess an architectural characteristic.
- Metric-based: track cyclomatic complexity, LOC, and depth of inheritance tree (DIT) to alert when maintainability thresholds are exceeded, prompting analysis and possible refactoring.
- Performance and security tests confirm recent changes have not degraded those characteristics.
- Chaos/resilience engineering: Netflix’s Chaos Monkey deliberately disables a machine to verify fault tolerance — a holistic, continuous fitness function scheduled during business hours so engineers can respond.
Making incremental changes
Section titled “Making incremental changes”- Incremental change covers how changes are introduced, built, and deployed.
- Smaller-scope changes are easier to understand, lower the risk of defects, and are easier to code-review and test.
- Ties back to Lehman’s Law VIII (feedback): agile methods and DevOps practices like CI and CD speed up the feedback loop (see DevOps practices).
- CI: developers integrate frequently; frequent commits reduce merge conflicts, and automated builds with automated testing give fast feedback on a small batch of changes.
- CD: releasing changes to production safely, repeatably, and sustainably — low-risk, high-quality releases with faster time-to-market makes the system highly evolvable.
Architectural changes across multiple dimensions
Section titled “Architectural changes across multiple dimensions”- A system has many dimensions beyond code: the database and its data, security, performance, and the deployment environment.
- Architects tend to focus on technical aspects (language, frameworks, libraries) but must consider all dimensions to keep the system evolvable as changes are made.
Loosely coupled architectures
Section titled “Loosely coupled architectures”- Coupling is the degree of dependency between components (see Designing orthogonal software systems).
- Tight coupling makes change harder: a change to one component is more likely to ripple into others, increasing total effort for development and testing.
- Minimize dependencies and make components as independent as possible. Loose coupling reduces complexity, tends to increase cohesion, and improves maintainability.
- Example: decouple cross-cutting concerns from application logic — ideally as their own services depended on via interfaces — so concerns like caching and logging can evolve independently, reducing duplication.
Designing evolvable APIs
Section titled “Designing evolvable APIs”- You cannot anticipate future API changes. Message contracts may change: amount of data sent/received, field names or data types, or new data representations for new client types.
- APIs — especially once published — should be designed to evolve without breaking existing clients.
Applying Postel’s Law to APIs
Section titled “Applying Postel’s Law to APIs”- The Robustness Principle (Postel’s Law): be conservative in what you send, liberal in what you accept. Originally from TCP design, applicable to message contracts.
- Conservative in what you send: expose only the minimum data necessary, lessening the chance a breaking contract change is needed.
- Liberal in what you accept: consumers should extract only what they need and ignore the rest, so future changes to unused parts do not break them.
Using standards in your software system
Section titled “Using standards in your software system”- Choosing standards (language, framework, library, protocol, database, data-interchange format, tools) increases maintainability and eases evolution.
- Standards make integration with other systems easier and make it easier to find people familiar with the technologies over a long maintenance life.
- A standard is not always the best choice, but it is a factor architects should weigh given the benefits.
Last responsible moment (LRM)
Section titled “Last responsible moment (LRM)”- LRM: delay a decision until the cost of not deciding exceeds the cost of deciding.
- Traditional architectures decided very early; evolutionary architectures defer important, hard-to-reverse decisions to the LRM to avoid premature, costly-to-rework choices and to gather maximum information.
- The challenge: it is hard to know when the LRM actually is, since costs and benefits shift. Deferring unimportant decisions has little benefit — reserve LRM for decisions important to the architecture.
Related concepts
Section titled “Related concepts”Citations
Section titled “Citations”- Software Architect’s Handbook (Packt, 2018), Ch.16 “Designing evolutionary architectures”, pp. 1144-1158.