Evolutionary Architecture (Ford/Parsons/Kua, 2017, 2nd ed 2022): architecture supports guided incremental change. Fitness functions = automated checks of architectural characteristics in CI. Architectural quanta = independently deployable cohesive units. Strangler Fig pattern (Fowler) for legacy migration. Modular monolith ready for split. Topology: Repo + CI pipeline (lint, unit tests, architecture fitness via dependency-cruiser/ArchUnit, performance fitness via k6, security fitness via gitleaks/Snyk, API contract fitness via Pact, cost fitness via Infracost) -> gate -> canary -> production with continuous SLO + chaos engineering + auto-rollback. Includes Strangler Fig migration setup (facade, legacy, new service, shadow mode, feature flags) and modular monolith (catalog/orders/payments/billing modules with in-process bus) ready to split into 2 architectural quanta with own DB and Kafka. 4 scenarios: (1) fitness function fails CI on cyclic deps blocks PR; (2) Strangler Fig endpoint migration with shadow mode and gradual cutover; (3) modular monolith ready-for-split extracts payments quantum for PCI-DSS compliance without rewriting; (4) continuous + holistic fitness in production with auto-rollback on canary breach.
Архитектура не статичная: требования к performance, security, compliance, scale меняются месяцами. Классический «Big Up Front Design» через год превращается в legacy, потому что никто не охраняет изначальные свойства. Evolutionary Architecture (Neal Ford, Rebecca Parsons, Patrick Kua, 2017) предлагает: архитектура должна поддерживать guided, incremental change across multiple dimensions, а ключевые characteristics проверяются автоматически — fitness functions, как тесты, но для архитектурных свойств (modularity, latency, coupling, security).
Без fitness functions всё, что не проверяется в CI, через 6 месяцев нарушено. Senior помнит правило «модули говорят только через /api», новичок коммитит import { OrderInternalEntity } from "../orders/internal" — code review пропустил в hurry, и через год катастрофически связанные модули.
Если архитектурное свойство не проверяется автоматически — оно нарушено.
Fitness function = тест на characteristic, который запускается в CI или в проде непрерывно. Atomic (одно правило) vs Holistic (комбинация). Triggered (на PR) vs Continuous (в проде 24/7). Static (правило фиксировано) vs Dynamic (threshold адаптируется). Mandatory (блокирует deploy) vs Informational (только dashboard).
Второй ключевой концепт — architectural quantum: independently deployable artifact с high functional cohesion. Монолит = один quantum. Микросервис = quantum. Quantum boundary часто совпадает с bounded context (DDD). Эволюция = добавление/extraction quanta, и она дёшева, если fitness functions держали boundaries чистыми с самого начала.
Третий — Strangler Fig (Martin Fowler, 2004): incremental migration legacy через facade, vertical slice за раз, shadow mode для проверки эквивалентности, gradual cutover через feature flags. Альтернатива Big Bang rewrite, которая исторически убивает компании (Netscape 6.0, см. Joel Spolsky).
Четыре ряда, каждый — отдельный аспект эволюционной архитектуры.
Row 1 — Source + CI Pipeline. Repo с модулями catalog/orders/payments/billing. Pull request triggerит цепочку gates: lint -> unit -> fitness-arch (dependency-cruiser / ArchUnit) -> fitness-perf (k6, p99 budget) -> fitness-sec (gitleaks, Snyk, Semgrep) -> fitness-api (Pact, Spectral, Buf) -> fitness-cost (Infracost per-PR diff) -> gate (aggregate). Только при passing всех mandatory fitness идёт deploy в canary. Это triggered + atomic проверки.
Row 2 — Architectural Quanta. Слева modular monolith с четырьмя модулями и in-process event bus — один quantum, один deploy, один процесс. Справа — то же приложение после Strangler-extract payments в отдельный quantum: два сервиса, две DB (payments DB изолирована PCI-DSS), Kafka как cross-quantum event bus. Извлечение payments стало возможным потому что architecture fitness functions раньше не давали модулям импортировать internals друг друга.
Row 3 — Strangler Fig. Client -> Facade (Envoy/API Gateway) -> либо Legacy PHP-монолит, либо new Go-сервис. Facade routes по правилу + feature flag (1% -> 10% -> 100%). Shadow comparator посылает запрос обоим бэкендам и сравнивает ответы async — sync ответ всё ещё возвращает legacy до полного cutover.
Row 4 — Continuous fitness in production. Canary deploy (5%) -> fleet (95%), оба emit метрики в SLO monitor. Chaos Monkey injectит failures в fleet. SLO breach триггерит pager И auto-rollback (deployment fitness). Rollback возвращает previous release за 90s. Pager -> code = feedback loop developer-у.
Edges не «направление данных», а физические соединения; обратные потоки (response от service к client, OrderPlaced consumer reading) идут reverse animation по тем же edges.
fitness-blocks-bad-pr — Developer добавляет cross-module import, ArchUnit/dependency-cruiser ловит на gate fitness-arch, PR red, merge заблокирован, автор читает rationale, выбирает между публичным API, локальной проекцией type или event-driven подходом. Демонстрирует atomic + triggered + static + mandatory fitness function и почему «warning instead of error» — антипаттерн.
strangler-fig-migration — Полный цикл миграции одного endpoint GET /api/users/:id/profile. Baseline 100% legacy -> Build new-svc -> Shadow mode 2 недели (находят timezone bug) -> Gradual cutover 1% / 10% / 50% / 100% через LaunchDarkly -> Remove legacy code -> Repeat для следующего slice. Заканчивается outcome: за 18 месяцев 80% endpoints на new stack, deploy 50x/день вместо 1x/2 недели. Контрапример: Netscape 6.0 / Wasabi rewrite.
quantum-split-modular-monolith — PCI-DSS auditor требует изоляции payments. Modular monolith готов к extract потому что: (1) architecture fitness держал module boundaries чистыми, (2) Pact-контракты на public API, (3) Buf-versioned event schemas. Extract = move directory + замена in-process bus на Kafka. Дни вместо месяцев. Иллюстрирует тезис Sam Newman («Monolith to Microservices»): extract по конкретному driver (compliance / scaling / team autonomy / failure isolation), не «потому что microservices модно».
continuous-fitness-prod — Deploy v2.4.1 в canary (5%). Holistic fitness function мониторит триаду (latency_p99 > baseline*1.5) AND (error_rate > 0.2%) AND (conversion_drop > 15%). На t+60s — все три true, auto-rollback за 90s, 95% users не видели degradation. Параллельно Chaos Monkey kill random pods и измеряет resilience continuously. Netflix pioneered это в 2010. Google SRE error budget = formalized fitness function для reliability.
Главный вопрос — не «нужны ли fitness functions», а когда инвестировать. Initial setup 2-4 недели senior + 5-10% maintenance forever. На прототипе это overhead, который замедляет experimentation. На продукте 50+ engineers без них = гарантированный architectural drift.
Threshold-based подход: solo / hackathon — никаких fitness, code review достаточно. Early-stage (2-5 eng) — только security fitness (gitleaks, npm audit) — ROI очевиден. Growth-stage (5-20 eng) — добавить architecture (dependency-cruiser) и API contract (Pact) fitness. Mature (20+ eng) — full set с owners, runbooks и quarterly reviews. Без owner fitness function через 6 месяцев = noise, который отключают.
Strangler Fig vs Big Bang Rewrite: default — всегда Strangler. Big Bang оправдан только если legacy не в проде (rare). Strangler требует facade, vertical slices (тонкие и complete), shadow mode для confidence, gradual cutover через feature flag, sunset date заранее (без неё двойная система навсегда). Если через 18 месяцев migration буксует — STOP и переоценить.
--no-verify, правило отключают глобально через полгода. Каждой fitness нужен owner team + runbook «что делать когда упало».