Evolutionary Architecture
Contents
- Key Claims
- Definition
- Evolvability as a Meta-Characteristic
- The Three Pillars
- Evolvability by Architecture Style
- Refactoring vs Restructuring
- Adaptation vs Evolution
- Guidelines for Building Evolvable Architectures
- Fitness Function Categories for API Systems
- Hypothesis-Driven Development
- API Layer Cake (Antipattern)
- Major Antipatterns (evo-arch Ch 7)
- APIs as Seams (api-arch perspective)
- Strangler Fig Pattern
- The Six Rs of Cloud Migration
- Type 1 vs Type 2 Decisions
- When to Build Evolutionary Architecture (and When Not to)
- Form Follows Failure (Nygard)
- Bad Layering as an Evolutionary Obstacle (Nygard)
- Decision Loop Speed and Thrashing
- How Different Sources Treat It
- Related Concepts
- Key Takeaways
Key Claims
- Three pillars. Incremental change (Deployment Pipelines make small changes safe), fitness functions (objective automated governance), and appropriate coupling (matching coupling tightness to business necessity).
- Inappropriate coupling is the root cause of non-evolvability. Most architectures fail to evolve because their coupling didn't match the change vector. The decoupling lever varies by axis: deployment, runtime, data, schema, organisational.
- Evolvability varies by architecture style. Microservices (highest), event-driven (high), service-based (medium-high), down to Big Ball of Mud (none). The limiting factors are quantum size and the presence of cross-quantum transactions.
- Migration follows established patterns. Strangler Fig for external seams, Branch By Abstraction for embedded capabilities, Parallel Run for correctness verification. Big-bang rewrites have a high failure rate; the safe path is incremental.
- Architecture decisions are reversible at different costs. Type 1 decisions (irreversible) warrant ADRs and extensive deliberation; Type 2 (reversible) deserve experimentation. Treating Type 2 decisions as Type 1 is the dominant antipattern.
- Testability + deployability are the operational drivers. Farley's framing: an evolvable architecture is one whose individual components can be tested in isolation and deployed independently. TDD acts as real-time architectural feedback — code that's hard to test is poorly modularised.
- Form follows failure. Architectural patterns emerge in response to specific past failures (Sidecar emerged because cross-cutting concerns kept breaking; Circuit Breaker because cascading failures kept recurring). Knowing the failure makes the pattern make sense.
Definition
"An evolutionary architecture supports guided, incremental change across multiple dimensions." — Ford, Parsons, Kua (→ Building Evolutionary Architectures Ch 1)
Three words carry the weight:
- Guided — change is directed by fitness functions and architectural principles, not left to drift
- Incremental — changes are small and safe, not big-bang rewrites; supported by deployment pipelines and automated tests
- Multiple dimensions — not just code structure but also data, security, performance, operational, and compliance concerns evolve simultaneously
Evolutionary architecture is not the same as "agile architecture" or "emergent design." It requires deliberate upfront identification of the dimensions that matter and explicit fitness functions to govern those dimensions continuously.
Evolvability as a Meta-Characteristic
Evolvability is not simply one more "-ility" alongside scalability and availability — it is a meta-characteristic: an architectural wrapper that protects all the other architectural characteristics as the system changes over time (→ Building Evolutionary Architectures Ch 1).
The architectural bit-rot problem: architects design a system with deliberate characteristics (scalability, performance, security), then expose it to real-world development. Developers gradually erode those characteristics — bypassing layering for performance, adding tight coupling for convenience. Without active protection, the architecture degrades silently. Fitness functions are the protection mechanism.
"Adding evolvability as an architectural characteristic implies protecting the other characteristics as the system evolves. Thus, evolvability is a meta-characteristic, an architectural wrapper that protects all the other architectural characteristics." (Ch 1)
The term "evolutionary" was chosen deliberately over alternatives:
- Adaptable/agile: captures change over time but says nothing about the direction of change
- Emergent: implies change arises without intent — the opposite of guided
- Incremental: captures only one of the three pillars, not the governance aspect
- Evolutionary: implies both the process (small guided changes) and the goal (fitness-tested improvement)
The Three Pillars
Building Evolutionary Architectures organises the discipline around three mutually reinforcing pillars (→ Building Evolutionary Architectures Ch 1):
1. Incremental change — the operational capability to make small, safe changes frequently. Enabled by Deployment Pipelines, automated fitness functions, and high test coverage. Measured by cycle time: v ∝ c (evolution speed is proportional to cycle time).
2. Fitness functions — the governance mechanism. Any mechanism that provides an objective, repeatable assessment of an architectural characteristic (→ Fitness Functions). Includes automated tests, architectural checks, chaos engineering, monitoring alerts, and manual reviews. Fitness functions are identified upfront for each dimension and automated in the deployment pipeline.
3. Appropriate coupling — matching coupling tightness to business necessity. Inappropriate coupling (shared databases without schema ownership, shared libraries coupling microservices, vendor lock-in) is the primary cause of architectural non-evolvability. The architecture quantum (→ Architecture Quantum) defines the scope of coupling that must be managed.
Evolvability by Architecture Style
Evo-arch rates each major architecture style for evolvability based on coupling, quantum size, and independent deployability (→ Building Evolutionary Architectures Ch 4):
| Style | Evolvability | Primary Limiting Factor |
|---|---|---|
| Big Ball of Mud | None | No coupling control; no fitness function surface |
| ESB-driven SOA | Poor | Central orchestration hub; vendor coupling |
| Mediator EDA | Low | Mediator reintroduces coupling |
| Layered monolith | Low–Medium | Technical partitioning; full deployment required for any change |
| Microkernel | Medium | Plug-in contracts; single quantum |
| Service-based | Medium–High | Shared database; coarse quanta |
| Broker EDA | High | Decoupled event channels; fine-grained quanta |
| Microservices | Highest | Maximum decoupling; smallest practical quanta |
Transactions as a constraint: two-phase commit across service boundaries effectively merges those services into a single quantum (the "strong nuclear force" of architecture). This is why service-based architectures — which support ACID transactions within coarse services — score lower on evolvability than microservices, which require saga/eventual consistency patterns instead.
Refactoring vs Restructuring
A distinction the book introduces in Ch 6 that is often blurred: refactoring (Fowler's definition) changes internal structure without changing external behaviour — the same inputs produce the same outputs, and architectural characteristics are preserved. Restructuring changes the architectural characteristics themselves: you are not preserving the old behaviour, you are changing what the system optimises for.
Fitness functions are the protection mechanism during restructuring: they continuously verify that the characteristics being preserved (performance, security, data integrity) remain intact as the internal structure changes. The GitHub Scientist pattern (Ch 3) is the canonical restructuring tool — it runs old and new code paths simultaneously, compares outputs, and guards against regressions without stopping production traffic.
"Refactoring means preserving external behaviour. Many architectural changes don't actually preserve external behaviour—they change priorities." (Ch 6)
Adaptation vs Evolution
A further distinction from Ch 8: adaptation layers new behaviour alongside old — the original implementation is preserved and new capabilities are bolted on. Adaptation is sometimes necessary (feature flags, compatibility shims) but accumulates technical debt proportional to the number of adaptation cycles. Each adaptation cycle increases the number of parallel code paths.
Evolution changes the architecture in situ, protected by fitness functions. The end result is a system that continues to change without accumulating a legacy of outdated solutions. The goal of evolutionary architecture is to support evolution, not merely adaptation.
Feature flags are an example of intentional, bounded adaptation: used temporarily during A/B experiments or incremental rollouts, then removed once the decision is resolved. The Knight Capital incident demonstrates what happens when temporary adaptations become permanent: the retained feature flag, reactivated on one un-updated server, cost $440M in 45 minutes.
Guidelines for Building Evolvable Architectures
Practical guidelines from evo-arch Ch 6:
| Guideline | Implication |
|---|---|
| Remove needless variability | Immutable infrastructure; no snowflake servers |
| Make decisions reversible | Blue-green deployments; feature flags (clean up after each migration) |
| Prefer evolvable over predictable | Design for unknown unknowns; avoid premature optimisation for predicted requirements |
| Build anticorruption layers JIT | Abstract external dependencies; delay commitment to the last responsible moment |
| Build sacrificial architectures | Intentional throw-away systems for MVP; replace when validated |
| Mitigate external change | Prefer pull updates (libraries) to push updates (frameworks); wrap transitive dependencies |
| Libraries vs frameworks | Libraries are passive (update when needed); frameworks are active (update aggressively — they drive your architecture) |
| Prefer Continuous Delivery to snapshots | Snapshots create speculative updates and stale dependencies |
| Version services internally | Hide multiple API versions within the service; support no more than two simultaneously |
Fitness Function Categories for API Systems
Chapter 8 of Mastering API Architecture elaborates the fitness function categories specifically for API-based systems (→ Mastering Api Architecture Ch 8):
| Category | What to Measure |
|---|---|
| Code Quality | Test coverage, cyclomatic complexity, static analysis results |
| Resiliency | Error rate in pre-production under synthetic traffic; fault injection via gateway or mesh |
| Observability | All services publish required metric types (RED); structured log format compliance |
| Performance | Latency and throughput targets; automated in the build pipeline; requires production-like data |
| Compliance | Audit requirements, data governance rules, GDPR/PCI evidence |
| Security | Dependency vulnerability scans; automated OWASP-style static analysis |
| Operability | Monitoring and alerting configured; runbooks exist; on-call rotation in place |
ADRs should document the initial fitness function decisions — what to measure, what threshold constitutes failure, and what pipeline stage the check runs in.
Hypothesis-Driven Development
A technique enabled by evolutionary architecture that replaces requirements with experiments (→ Building Evolutionary Architectures Ch 3). Rather than gathering formal requirements and building features based on analyst judgement, teams frame new capabilities as hypotheses:
"If we make the sales images bigger, we hypothesize that it will lead to a 5% increase in sales for those items."
The hypothesis specifies: what is being tested, what experiment will be run, and what confirming the hypothesis means for future development. Experiments run via A/B testing — a portion of users see the proposed change; the rest see the existing behaviour. Results from real user behaviour confirm or refute the hypothesis before committing to the full feature.
Why it matters architecturally: hypothesis-driven development requires service-based or microservices architecture to support simultaneous multiple versions of a feature, modern DevOps practices for fast deployment of experiment variants, and feature flags to route users to different variants. It is only possible when cycle time is short enough to run meaningful experiments.
The mobile.de case: a product accumulated features over years that degraded the overall user experience, but no analyst knew which features were valuable. Rather than debating which of three UI approaches to take, the team built all three variants and let users decide through A/B testing. Evolutionary architecture enables this by making variant creation and routing inexpensive.
"Experiments should run long enough to yield significant results. Generally, it is preferable to find a measurable way to determine better outcomes rather than annoy users with things like pop-up surveys." (Ch 3)
API Layer Cake (Antipattern)
The "layered APIs" or "API layer cake" pattern applies the classic enterprise tier model to APIs: Systems of Engagement (SoE) → Systems of Differentiation (SoD) → Systems of Record (SoR), based on Gartner's Pace-Layered Application Strategy (→ Mastering Api Architecture Ch 8).
This pattern has a poor track record for the same reasons as layered monoliths: it encourages shortcuts (presentation tier directly calls the datastore tier), leads to functionality duplication between layers, and creates a situation where any change to a business capability requires modifying multiple layers in lockstep. Generally avoid this pattern in favour of domain-oriented, highly cohesive APIs aligned to bounded contexts.
Major Antipatterns (evo-arch Ch 7)
| Antipattern | Summary |
|---|---|
| Vendor King | Architecture coupled to a vendor's product roadmap; vendor constraints become architectural constraints |
| Code Reuse Abuse | Shared libraries coupling microservices; prefer duplication to coupling in distributed architectures |
| Reporting | OLTP and OLAP in the same service; analytical queries create inadvertent coupling to operational data models |
| Inappropriate Governance | One-size-fits-all governance; solution is Goldilocks Governance (three technology stacks: simple/medium/complex) |
| Planning Horizons | Sunk cost fallacy applied to architecture; irrational attachment to past decisions prevents needed restructuring |
See Evolutionary Database Design for the Reporting antipattern in depth.
APIs as Seams (api-arch perspective)
Michael Feathers' concept of a seam — a place where loosely coupled substitution is possible — applies directly to API boundaries (→ Mastering Api Architecture Ch 8). An API is a seam because:
- The consumer depends on the contract (OAS, .proto), not the implementation
- The producer can be replaced, replatformed, or rewritten without the consumer knowing, as long as the contract is honoured
- The API gateway provides location transparency: the consumer calls a stable route regardless of where the backend is deployed
Seams are why API-first design matters architecturally: a well-designed contract makes the implementation replaceable; a poorly designed one couples the consumer to implementation details and eliminates the seam.
Strangler Fig Pattern
The canonical pattern for migrating a legacy system to a new architecture without a full rewrite (→ Mastering Api Architecture Ch 8):
- Facade: deploy an API gateway or facade in front of the legacy system
- Parallel services: implement new capabilities as separate services behind the facade
- Route migration: progressively route traffic from the legacy to new services (canary → full cutover)
- Retire: decommission the legacy when it handles no traffic
Facade vs adapter: the gateway should act as a facade (routing transparently), not an adapter (transforming data models or business logic). Adapter logic in the gateway creates coupling between gateway configuration and domain schema.
The Six Rs of Cloud Migration
Framework for classifying migration decisions per component (→ Mastering Api Architecture Ch 9):
| Strategy | Description | When to Use |
|---|---|---|
| Retain | Keep on-premises | Regulatory constraint, dependency blocker |
| Rehost | Lift and shift | Large estate, short timeline; minimal benefit |
| Replatform | Move with targeted optimisations | Some cloud benefit without full refactor |
| Repurchase | Replace with SaaS | Commodity functions (CRM, HRIS) |
| Refactor / Re-architect | Redesign cloud-native | Competitive differentiation, scalability |
| Retire | Decommission | Unused, superseded, or consolidated |
The strangler fig is the execution mechanism for Replatform and Refactor strategies.
Type 1 vs Type 2 Decisions
Not all architectural decisions carry the same reversibility cost (→ Mastering Api Architecture Ch 8):
- Type 1 (irreversible): API gateway selection, service mesh adoption, exchange format (REST/gRPC), authentication framework. These create path dependencies across many consumers. Must be deliberate and documented in Adrs.
- Type 2 (reversible): deployment strategies, feature flag tooling, observability vendors. Can be changed without breaking consumer contracts; lighter process is appropriate.
When to Build Evolutionary Architecture (and When Not to)
From Ch 8: evolutionary architecture is not appropriate in all contexts.
Build it when:
- Cycle time is a competitive differentiator in your market (A/B testing, hypothesis-driven development, fast response to market shifts require short cycle times)
- Scale matters (coupling at any architectural point eventually limits scale; decoupling enables both evolvability and scale — the Amazon monolith-to-microservices story)
- Advanced DevOps capabilities are desired (A/B testing is structurally impossible in highly coupled systems)
- The business faces the Innovator's Dilemma: disruption can come from smaller competitors with faster, more evolvable systems
Do not build it when:
- The architecture is an irredeemable Big Ball of Mud: the effort to make it evolvable exceeds the cost of rewriting from scratch. First, find the modularity that does exist; if the untangling cost is too high, rewrite is the answer.
- Other architectural characteristics dominate: LMAX achieved 6 million transactions/second on a single Java thread by fitting logic into CPU cache and pre-allocating memory to prevent GC. That extreme optimisation for throughput makes evolvability secondary — the architecture is domain-specific, not general.
- The system is deliberately sacrificial: MVP architectures exploring market viability should be designed to throw away. Martin Fowler coined the term; Twitter's first architecture is the canonical example. Replace when validated; do not invest in evolving something intended to be replaced.
- Short business horizon: if the company plans to close or pivot completely within a year, evolvability investment is waste.
Form Follows Failure (Nygard)
Nygard (→ Release It ch. 16) draws on Henry Petroski's The Evolution of Useful Things to reframe how architectural evolution actually happens: "Form follows failure, not function." Changes to design — of physical artifacts and software systems alike — are motivated more by what the prior version does poorly than by what it does well. Systems evolve through their failures; the initial design is rarely the driver of long-term shape.
The practical implication: the most honest way to understand why a system looks the way it does is to study the failures and constraints it was designed to work around, not the stated requirements.
Bad Layering as an Evolutionary Obstacle (Nygard)
Standard layered architectures enforce vertical isolation (UI / domain / persistence) but tolerate — and encourage — horizontal coupling: "god" domain classes that everything touches, where any change requires a drilling expedition through all layers and produces commits touching Foo, FooController, FooFragment, FooMapper, FooDTO.
The problem: one layer's decomposition dominates the others. When a new concept enters the domain, it casts shadows through every layer. This is not a layer isolation problem — it is a coupling problem in disguise.
Component-based decomposition (rotating the barriers 90 degrees): each component owns its full stack from storage through API/UI; components communicate only through narrow formal interfaces; components are substitutable independently. If components run in separate processes, this is microservices. If in the same process, it resembles "self-contained systems."
Each component boundary creates an option (→ Baldwin & Clark's modular operators) to split, substitute, augment, or exclude the component independently.
Decision Loop Speed and Thrashing
Nygard (→ Release It ch. 16) frames organisational adaptability as the rate of the decision loop: sense → decide → act → observe. Getting inside the competitor's decision loop (acting faster than they can react) is the decisive competitive advantage.
Thrashing is the failure mode of going too fast: action rate exceeds feedback rate, so each new direction is initiated before the prior one has been evaluated. Aviation calls this "pilot-induced oscillation" (porpoising). Fix: do not slow down delivery; instead, speed up the feedback side — build an experimentation platform to accelerate observation and decision, rather than deploying more build tooling.
How Different Sources Treat It
| Source | Perspective |
|---|---|
| Building Evolutionary Architectures | Originating source; formal definition, three pillars, evolvability ratings per style, guidelines, antipatterns — evolutionary architecture as the central subject |
| Release It | "Form follows failure" (Petroski); bad layering as horizontal coupling obstacle; component-based decomposition; modular operators (Baldwin & Clark); decision loop speed and thrashing; service extinction as the critical evolutionary mechanism |
| Mastering Api Architecture | API-specific angle: APIs as seams, strangler fig at gateway level, six Rs for cloud migration, Type 1/2 decisions; fitness functions for API governance |
| Fundamentals Of Software Architecture | Applies evolutionary architecture thinking to monolith-to-microservices migration; fitness functions as one component of the governance layer |
| Learning Domain Driven Design | DDD-centric brownfield modernisation: (1) Strategic analysis (understand business domain → identify subdomains → chart context map of current design); (2) Strangler pattern used with an ACL/OHS layer — facade routes traffic; legacy and modernised context may temporarily share a database; (3) Refactoring tactical design incrementally (start with value objects, then aggregates, don't jump to event sourcing); (4) "Pragmatic DDD": DDD is about business-domain-driven design decisions, not mandatory use of all patterns; "undercover DDD" — use tools individually without requiring org-wide adoption (ch. 13) |
| Monolith To Microservices | Migration-focused treatment: strangler fig as the primary incremental extraction pattern (HTTP proxy, FTP, and message variants; deployment ≠ release; feature freeze during migration); branch by abstraction for deeply embedded capabilities; parallel run and verify variant for correctness verification; progressive delivery as the umbrella strategy. Positions evolutionary architecture practices as prerequisites for safe microservice migration (ch. 3). |
| Software Architecture Metrics | Farley (ch. 3): testability and deployability as the two operational drivers of evolutionary architecture; designing for testability produces the five sustainable design attributes (modularity, cohesion, SoC, abstraction, coupling); TDD as real-time architectural feedback; deployment pipeline scope = independently deployable unit; architectural descriptions as "tourist maps" — highlight landmarks, suppress irrelevant detail; do not over-engineer for hypothetical requirements, keep options open through testability and short deploy cycles. |
Related Concepts
- Fitness Functions — the governance mechanism that makes evolution safe
- Deployment Pipelines — the automation mechanism for incremental change
- Architecture Quantum — quantum size as the primary evolvability lever
- Conways Law — team structure as an architectural decision; Inverse Conway Maneuver
- Evolutionary Database Design — data dimension of evolutionary architecture
- Adrs — documents irreversible decisions that define the evolution path
- Api Design — contract stability that makes incremental migration possible
- Api Gateway — provides location transparency enabling the strangler fig
- Modular Monolith — intermediate migration step: domain-partitioned single deployment with fitness-function-enforced coupling, before extracting services
- Strangler Fig — the primary pattern for incremental migration of an existing system
- Branch By Abstraction — for migrating deeply embedded capabilities with no external seam
- Parallel Run — correctness verification during coexistent dual-implementation phases
- Progressive Delivery — staged rollout patterns (canary, blue-green, ring, dark launch, feature flag) that make evolutionary change safe
- Feature Flags — application-layer release control; deployment ≠ release at the code level
- Cost As Architectural Force — change cost as a compounding architectural property
Key Takeaways
- Build for evolution, not for the end state. The three pillars (incremental change, fitness functions, appropriate coupling) make change cheap; designing for an imagined future is usually wasted work.
- Coupling decisions are evolvability decisions. Inappropriate coupling — coupling tighter than the change axis requires — is the dominant cause of non-evolvability. Loosen coupling along the axes that actually change.
- Migration is incremental. Strangler fig for external seams, branch by abstraction for embedded capabilities, parallel run for correctness checks. Big bang rewrites have a high failure rate that doesn't drop with team experience.
- Type 1 decisions deserve ADRs; Type 2 deserve experiments. Treating Type 2 (reversible) decisions as Type 1 (irreversible) is the dominant antipattern; teams over-deliberate on choices they could just try.
- Testability and deployability are the operational drivers. If individual components can't be tested in isolation and deployed independently, the architecture is not evolvable regardless of style.