土法炼钢兴趣小组的算法知识备份

【系统架构设计百科】架构治理:适应度函数与演进式架构

文章导航

分类入口
architecture
标签入口
#governance#fitness-function#ArchUnit#evolutionary-architecture#ADR

目录

架构决策写在文档里,三个月后没人记得;架构评审会上达成的共识,半年后被新来的同事无意打破。这是每一个经历过大型系统演进的架构师都深有体会的痛点。当系统规模超过 50 个微服务、团队人数突破 200 人时,仅靠文档和评审来守住架构约束,几乎不可能。Netflix 在 2018 年提出的「铺好的路(Paved Road)」策略,以及 Neal Ford 等人在《演进式架构(Building Evolutionary Architectures)》中系统阐述的适应度函数(Fitness Function)理念,为架构治理提供了一条自动化、可度量、可持续演进的路径。本文将从治理困境出发,深入剖析适应度函数的设计与实现,结合 ArchUnit、NetArchTest、架构决策记录(Architecture Decision Record,ADR)等工具和实践,探讨如何构建一套贯穿开发、构建、部署全生命周期的架构治理体系。

一、架构治理的困境与演进

1.1 传统治理模式的局限

传统的架构治理通常依赖以下手段:

这些手段在小规模团队中尚可运作,但随着系统和团队规模增长,暴露出三个核心问题:

问题维度 具体表现 影响
滞后性 文档更新总是落后于代码变更 新成员无法获取准确的架构约束信息
主观性 评审标准因人而异,缺乏一致判定 同类违规有时放行、有时驳回
不可扩展 人工审查无法覆盖每一行代码变更 架构侵蚀(Architecture Erosion)逐步累积

1.2 架构侵蚀的代价

根据 Gartner 2024 年的调研数据,超过 68% 的技术债务源于架构层面的约束被打破。一个典型案例:某电商平台最初设计了清晰的四层架构(展示层、应用层、领域层、基础设施层),但经过三年迭代后,领域层直接调用了展示层的 DTO 类多达 347 处,基础设施层被 12 个业务模块直接依赖。重构成本估算超过 4800 人天。

1.3 从「管控」到「引导」

演进式架构(Evolutionary Architecture)的核心理念是:架构不是一次性设计完成的产物,而是随业务持续演进的活系统。治理的目标也从「防止变更」转向「引导变更方向」。这一转变催生了两个关键实践:

  1. 适应度函数:将架构约束编码为可自动执行的测试,在 CI/CD 流水线中持续验证。
  2. 架构决策记录:将架构决策的上下文、动机、后果以结构化方式记录,形成可追溯的决策链。
graph TD
    A[架构愿景] --> B[架构原则]
    B --> C[架构约束]
    C --> D[适应度函数]
    C --> E[架构决策记录]
    D --> F[CI/CD 流水线]
    E --> F
    F --> G[自动化治理反馈]
    G --> H{是否符合约束?}
    H -->|是| I[允许合并/部署]
    H -->|否| J[阻断并通知]
    J --> K[开发者修复]
    K --> F
    G --> L[治理仪表盘]
    L --> M[架构师审查趋势]
    M --> A

二、适应度函数:架构约束的自动化守护

2.1 概念定义

适应度函数(Fitness Function)借鉴了进化计算中的术语,指的是一种用于评估架构特征是否满足预期的客观度量机制。Neal Ford 在《演进式架构》中将其定义为:

适应度函数是一种目标函数,用于评估架构在某个维度上距离目标的程度。

关键特征包括:

2.2 适应度函数的分类

适应度函数可以从多个维度进行分类:

分类维度 类型 示例
执行时机 原子型(Atomic) 单元测试级别的依赖检查
执行时机 触发型(Triggered) 每次 CI 构建时执行
执行时机 持续型(Continuous) 生产环境的实时监控
评估范围 领域特定(Domain-specific) 订单模块不能依赖用户模块的内部类
评估范围 系统全局(System-wide) 所有服务的 P99 延迟不超过 200ms
结果类型 二元型(Binary) 通过或失败
结果类型 度量型(Metric) 耦合度指数为 0.73

2.3 适应度函数的设计原则

设计高质量的适应度函数需要遵循以下原则:

  1. 明确性原则:每个函数只检查一个具体约束,失败时提供清晰的错误信息。
  2. 快速反馈原则:函数执行时间应控制在秒级,避免拖慢 CI 流水线。
  3. 可演进原则:约束的阈值应支持随时间调整(例如从「循环依赖不超过 5 处」逐步收紧为「零循环依赖」)。
  4. 低误报原则:宁可遗漏边界情况,也不要产生大量误报导致开发者忽视告警。

三、ArchUnit:Java 生态的架构守护者

3.1 ArchUnit 简介

ArchUnit 是一个面向 Java 生态的架构测试库,由 Peter Gafert 于 2017 年创建。它允许开发者用 Java 代码编写架构规则,并像普通单元测试一样在 JUnit 中执行。截至 2026 年,ArchUnit 已在 GitHub 上获得超过 3200 颗星,被包括 Deutsche Bank、Allianz、Zalando 在内的多家企业广泛采用。

核心能力:

3.2 基础用法:分层架构约束

以下示例展示如何用 ArchUnit 守护一个典型的四层架构:

package com.example.archunit;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.library.Architectures;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

class LayeredArchitectureTest {

    private static JavaClasses classes;

    @BeforeAll
    static void setup() {
        classes = new ClassFileImporter()
                .importPackages("com.example.myapp");
    }

    @Test
    void 分层架构约束_层间依赖方向必须自上而下() {
        Architectures.layeredArchitecture()
                .consideringAllDependencies()
                .layer("Controller").definedBy("..controller..")
                .layer("Service").definedBy("..service..")
                .layer("Domain").definedBy("..domain..")
                .layer("Infrastructure").definedBy("..infrastructure..")
                .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
                .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
                .whereLayer("Domain").mayOnlyBeAccessedByLayers("Controller", "Service")
                .whereLayer("Infrastructure").mayOnlyBeAccessedByLayers("Service", "Domain")
                .check(classes);
    }
}

3.3 包依赖规则

控制包之间的依赖关系是防止架构侵蚀的关键手段:

package com.example.archunit;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

class PackageDependencyTest {

    private static JavaClasses importedClasses;

    @BeforeAll
    static void setup() {
        importedClasses = new ClassFileImporter()
                .importPackages("com.example.myapp");
    }

    @Test
    void 领域层不得依赖控制器层() {
        ArchRule rule = noClasses()
                .that().resideInAPackage("..domain..")
                .should().dependOnClassesThat()
                .resideInAPackage("..controller..");

        rule.check(importedClasses);
    }

    @Test
    void 订单模块不得依赖用户模块的内部实现() {
        ArchRule rule = noClasses()
                .that().resideInAPackage("..order..")
                .should().dependOnClassesThat()
                .resideInAPackage("..user.internal..");

        rule.check(importedClasses);
    }

    @Test
    void Repository接口必须位于domain包中() {
        ArchRule rule = classes()
                .that().haveSimpleNameEndingWith("Repository")
                .should().resideInAPackage("..domain..");

        rule.check(importedClasses);
    }
}

3.4 命名规约与注解检查

package com.example.archunit;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RestController;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

class NamingConventionTest {

    private static JavaClasses classes;

    @BeforeAll
    static void setup() {
        classes = new ClassFileImporter()
                .importPackages("com.example.myapp");
    }

    @Test
    void Controller类必须以Controller结尾() {
        ArchRule rule = classes()
                .that().areAnnotatedWith(RestController.class)
                .should().haveSimpleNameEndingWith("Controller");

        rule.check(classes);
    }

    @Test
    void Service类必须以Service或ServiceImpl结尾() {
        ArchRule rule = classes()
                .that().areAnnotatedWith(Service.class)
                .should().haveSimpleNameEndingWith("Service")
                .orShould().haveSimpleNameEndingWith("ServiceImpl");

        rule.check(classes);
    }

    @Test
    void 所有公开API的DTO必须实现Serializable() {
        ArchRule rule = classes()
                .that().haveSimpleNameEndingWith("DTO")
                .should().implement(java.io.Serializable.class);

        rule.check(classes);
    }
}

3.5 循环依赖检测

循环依赖是架构侵蚀最常见的表现之一。ArchUnit 提供了便捷的循环依赖检测能力:

package com.example.archunit;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

class CyclicDependencyTest {

    private static JavaClasses classes;

    @BeforeAll
    static void setup() {
        classes = new ClassFileImporter()
                .importPackages("com.example.myapp");
    }

    @Test
    void 模块间不得存在循环依赖() {
        SlicesRuleDefinition.slices()
                .matching("com.example.myapp.(*)..")
                .should().beFreeOfCycles()
                .check(classes);
    }

    @Test
    void 各业务领域间不得存在循环依赖() {
        SlicesRuleDefinition.slices()
                .matching("com.example.myapp.domain.(*)..")
                .should().beFreeOfCycles()
                .check(classes);
    }
}

3.6 自定义规则:复杂约束的实现

当内置 API 不足以表达复杂的架构约束时,可以通过自定义规则来扩展:

package com.example.archunit;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

class CustomRuleTest {

    private static JavaClasses importedClasses;

    @BeforeAll
    static void setup() {
        importedClasses = new ClassFileImporter()
                .importPackages("com.example.myapp");
    }

    @Test
    void 聚合根的构造函数参数不得超过七个() {
        ArchRule rule = classes()
                .that().areAnnotatedWith(AggregateRoot.class)
                .should(haveConstructorWithMaxParameters(7));

        rule.check(importedClasses);
    }

    private static ArchCondition<JavaClass> haveConstructorWithMaxParameters(int max) {
        return new ArchCondition<>("构造函数参数不超过 " + max + " 个") {
            @Override
            public void check(JavaClass javaClass, ConditionEvents events) {
                javaClass.getConstructors().forEach(constructor -> {
                    int paramCount = constructor.getRawParameterTypes().size();
                    if (paramCount > max) {
                        String message = String.format(
                                "%s 的构造函数有 %d 个参数,超过上限 %d",
                                javaClass.getName(), paramCount, max
                        );
                        events.add(SimpleConditionEvent.violated(
                                javaClass, message
                        ));
                    }
                });
            }
        };
    }
}

四、其他语言生态的适应度函数工具

4.1 .NET 生态:NetArchTest

NetArchTest 是 .NET 生态中对标 ArchUnit 的架构测试库,由 Ben Morris 创建。以下是一个 C# 示例:

using NetArchTest.Rules;
using Xunit;

namespace MyApp.ArchTests
{
    public class LayerDependencyTests
    {
        [Fact]
        public void Domain层不得依赖Infrastructure层()
        {
            var result = Types.InAssembly(typeof(Domain.Order).Assembly)
                .That()
                .ResideInNamespace("MyApp.Domain")
                .ShouldNot()
                .HaveDependencyOn("MyApp.Infrastructure")
                .GetResult();

            Assert.True(result.IsSuccessful,
                "域层存在对基础设施层的违规依赖");
        }

        [Fact]
        public void Controller必须以Controller结尾()
        {
            var result = Types.InAssembly(typeof(WebApi.Startup).Assembly)
                .That()
                .Inherit(typeof(Microsoft.AspNetCore.Mvc.ControllerBase))
                .Should()
                .HaveNameEndingWith("Controller")
                .GetResult();

            Assert.True(result.IsSuccessful);
        }
    }
}

4.2 TypeScript/JavaScript 生态:dependency-cruiser

在前端和 Node.js 项目中,dependency-cruiser 是检查模块依赖关系的利器:

// .dependency-cruiser.js
module.exports = {
  forbidden: [
    {
      name: "领域层不得依赖UI层",
      severity: "error",
      from: { path: "^src/domain" },
      to: { path: "^src/ui" }
    },
    {
      name: "禁止循环依赖",
      severity: "error",
      from: { path: "^src" },
      to: { circular: true }
    },
    {
      name: "共享模块不得依赖业务模块",
      severity: "warn",
      from: { path: "^src/shared" },
      to: { path: "^src/(order|user|payment)" }
    },
    {
      name: "禁止直接依赖数据库驱动",
      severity: "error",
      from: { path: "^src/domain" },
      to: { path: "node_modules/(pg|mysql2|mongodb)" }
    }
  ],
  options: {
    doNotFollow: { path: "node_modules" },
    tsConfig: { fileName: "tsconfig.json" },
    reporterOptions: {
      dot: { theme: { graph: { rankdir: "LR" } } }
    }
  }
};

4.3 多语言适应度函数工具对比

工具 语言/平台 核心能力 社区活跃度 学习曲线
ArchUnit Java/Kotlin 分层约束、依赖检查、命名规则 高(3200+ Stars) 中等
NetArchTest .NET/C# 命名空间依赖、类型约束 中(800+ Stars)
dependency-cruiser JS/TS 模块依赖图、循环检测 高(5400+ Stars)
Deptrac PHP 层间依赖约束 中(700+ Stars)
go-arch-lint Go 包依赖规则 低(200+ Stars)
Konsist Kotlin Kotlin 专属架构测试 中(600+ Stars) 中等

五、适应度函数在 CI/CD 中的集成

5.1 集成策略

适应度函数应作为 CI/CD 流水线的一等公民(First-class Citizen),与单元测试、集成测试并列执行。推荐的集成策略如下:

graph LR
    A[代码提交] --> B[静态分析]
    B --> C[单元测试]
    C --> D[架构测试/适应度函数]
    D --> E[集成测试]
    E --> F[性能测试]
    F --> G[部署]
    
    D -->|失败| H[阻断流水线]
    H --> I[通知开发者]
    I --> J[查看违规详情]
    J --> K[修复并重新提交]
    K --> A
    
    style D fill:#f9a825,stroke:#f57f17,stroke-width:2px

5.2 GitHub Actions 配置示例

# .github/workflows/architecture-governance.yml
name: Architecture Governance

on:
  pull_request:
    branches: [main, develop]
  push:
    branches: [main]

jobs:
  arch-tests:
    name: 架构适应度函数检查
    runs-on: ubuntu-latest
    steps:
      - name: 检出代码
        uses: actions/checkout@v4

      - name: 配置 JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'

      - name: 缓存 Gradle 依赖
        uses: actions/cache@v4
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}

      - name: 执行架构测试
        run: ./gradlew archTest --no-daemon

      - name: 上传架构测试报告
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: arch-test-report
          path: build/reports/tests/archTest/

  dependency-check:
    name: 依赖关系合规检查
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 安装 dependency-cruiser
        run: npm install -g dependency-cruiser

      - name: 检查前端依赖合规
        run: depcruise --config .dependency-cruiser.js src/
        working-directory: frontend/

  metrics-collection:
    name: 架构度量指标采集
    runs-on: ubuntu-latest
    needs: [arch-tests]
    steps:
      - uses: actions/checkout@v4

      - name: 计算耦合度指标
        run: |
          ./gradlew jdepend --no-daemon
          python3 scripts/collect_arch_metrics.py

      - name: 上报指标到治理仪表盘
        run: |
          curl -X POST "${{ secrets.GOVERNANCE_API }}/metrics" \
            -H "Content-Type: application/json" \
            -d @build/arch-metrics.json

5.3 Gradle 配置示例

// build.gradle
plugins {
    id 'java'
}

dependencies {
    testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
}

// 将架构测试独立为单独的测试任务
sourceSets {
    archTest {
        java {
            srcDir 'src/arch-test/java'
        }
        compileClasspath += main.output + test.output
        runtimeClasspath += main.output + test.output
    }
}

configurations {
    archTestImplementation.extendsFrom testImplementation
    archTestRuntimeOnly.extendsFrom testRuntimeOnly
}

tasks.register('archTest', Test) {
    description = '执行架构适应度函数测试'
    group = 'verification'
    testClassesDirs = sourceSets.archTest.output.classesDirs
    classpath = sourceSets.archTest.runtimeClasspath
    useJUnitPlatform()

    reports {
        html.required = true
        junitXml.required = true
    }

    // 架构测试失败应阻断构建
    ignoreFailures = false
}

// 将架构测试纳入标准验证流程
check.dependsOn archTest

六、Netflix 的 Paved Road 策略:工业级架构治理案例

6.1 背景与挑战

Netflix 在 2018 年拥有超过 1000 个微服务,由 2000 多名工程师共同维护。面对如此庞大的系统规模,传统的架构治理方式完全失效。Netflix 的基础设施团队面临三大挑战:

  1. 标准化与自由度的矛盾:工程师文化强调自主权(Freedom & Responsibility),但完全不加约束会导致技术栈碎片化。
  2. 新服务创建成本高:每个新服务需要自行处理服务发现、负载均衡、断路器、可观测性等横切关注点。
  3. 安全与合规的一致性:在快速迭代中保持安全标准的一致性极为困难。

6.2 Paved Road 的核心理念

Netflix 的 Paved Road(铺好的路)策略基于一个核心洞察:与其告诉工程师「不能做什么」,不如让「正确的做法」成为最简单的选择

具体而言,Netflix 的做法包括:

6.3 Paved Road 的技术实现

graph TD
    subgraph "Netflix Paved Road 架构"
        A[Netflix Strata<br/>项目脚手架] --> B[服务模板]
        B --> C[标准化 Gradle 插件]
        B --> D[统一安全配置]
        B --> E[可观测性预集成]
        
        C --> F[依赖管理 BOM]
        C --> G[构建流水线配置]
        C --> H[架构测试预置]
        
        D --> I[OAuth2/mTLS 配置]
        D --> J[密钥管理集成]
        
        E --> K[Atlas 指标]
        E --> L[Zipkin 追踪]
        E --> M[结构化日志]
    end
    
    subgraph "治理反馈循环"
        N[合规扫描] --> O{符合 Paved Road?}
        O -->|是| P[绿色徽章<br/>优先支持]
        O -->|否| Q[黄色告警<br/>建议迁移]
    end
    
    H --> N

6.4 实施效果

Netflix 实施 Paved Road 策略后取得了显著效果:

指标 实施前(2017 年) 实施后(2020 年) 改善幅度
新服务创建时间 2-3 天 15 分钟 约 95%
服务符合安全基线比例 62% 94% +32%
生产事故中因配置错误导致的比例 34% 11% -23%
工程师对基础设施满意度 3.2/5 4.4/5 +37.5%
使用标准技术栈的服务比例 45% 89% +44%

6.5 可借鉴的实践

Netflix 的经验可以提炼为以下可复用的实践:

  1. 平台即产品:将内部平台团队当作产品团队运营,工程师是用户。
  2. 胡萝卜优于大棒:提供便利而非施加约束。
  3. 渐进式合规:不要求一步到位,而是通过自动化提醒逐步收敛。
  4. 度量驱动改进:持续收集 Paved Road 覆盖率、偏离原因等数据来改进平台。

七、架构决策记录(ADR):决策的可追溯性

7.1 ADR 的定义与价值

架构决策记录(Architecture Decision Record,ADR)是一种轻量级的文档格式,用于记录在架构演进过程中做出的重要决策。Michael Nygard 于 2011 年首次提出这一概念,至今已被 GitHub、Spotify、ThoughtWorks 等公司广泛采用。

ADR 的核心价值在于:

7.2 ADR 模板

以下是一个实用的 ADR 模板:

# ADR-{编号}: {决策标题}

## 状态

{提议 | 已接受 | 已废弃 | 已取代}

## 日期

{YYYY-MM-DD}

## 背景

{描述促成这一决策的上下文和问题陈述。
包括相关的技术约束、业务需求、团队现状等。}

## 决策

{清晰地陈述做出的架构决策。}

## 备选方案

### 方案一:{名称}
- 优点:{列出优点}
- 缺点:{列出缺点}
- 适用场景:{描述适用场景}

### 方案二:{名称}
- 优点:{列出优点}
- 缺点:{列出缺点}
- 适用场景:{描述适用场景}

## 决策理由

{解释为什么选择当前方案而不是其他备选方案。
引用具体的评估标准和权衡过程。}

## 后果

### 正面影响
- {列出正面影响}

### 负面影响
- {列出负面影响}

### 风险
- {列出潜在风险}

## 合规性检查

- [ ] 适应度函数已创建
- [ ] CI 流水线已集成
- [ ] 团队已知晓

## 相关决策

- 取代:{ADR-xxx}
- 关联:{ADR-xxx}

7.3 ADR 实例:消息队列选型

# ADR-042: 采用 Apache Kafka 作为核心事件总线

## 状态

已接受

## 日期

2026-03-15

## 背景

订单系统需要一个事件总线来实现跨服务的异步通信。
当前系统使用 RabbitMQ,但在日均 5000 万条消息的负载下出现了
以下问题:
1. 消息积压超过 200 万条时性能急剧下降
2. 缺乏原生的消息回溯(Replay)能力
3. 多消费者组场景下的扩展性不足

团队规模:12 名后端工程师,其中 4 名有 Kafka 使用经验。
运维团队已有 Kubernetes 环境,可使用 Strimzi Operator 部署。

## 决策

采用 Apache Kafka 3.7 作为核心事件总线,替换 RabbitMQ。

## 备选方案

### 方案一:升级 RabbitMQ 集群
- 优点:无需迁移成本,团队熟悉
- 缺点:无法根本解决消息回溯需求,水平扩展受限
- 适用场景:消息量低于 1000 万/天的场景

### 方案二:Apache Pulsar
- 优点:分层存储、原生多租户
- 缺点:社区生态较 Kafka 弱,团队无使用经验
- 适用场景:需要多租户隔离的 SaaS 场景

### 方案三:Apache Kafka
- 优点:高吞吐、消息回溯、成熟生态、团队有经验
- 缺点:运维复杂度较高,ZooKeeper 依赖(KRaft 模式可消除)
- 适用场景:大规模事件驱动架构

## 决策理由

1. Kafka 的日志追加模型天然支持消息回溯需求
2. 团队中 33% 的工程师有 Kafka 实战经验,降低学习成本
3. 使用 KRaft 模式可消除 ZooKeeper 依赖,简化运维
4. Kafka Connect 生态丰富,方便后续数据集成

## 后果

### 正面影响
- 消息吞吐能力提升至亿级/天
- 支持多消费者组独立消费同一 Topic
- 消息回溯窗口可配置(默认 7 天)

### 负面影响
- 迁移期间需要维护双系统(预计 6 周)
- JVM 调优和分区策略需要专项投入

### 风险
- 分区数过多可能导致 Leader 选举延迟
- 消费者 Rebalance 期间可能出现短暂中断

## 合规性检查

- [x] 适应度函数已创建(Kafka 延迟监控告警)
- [x] CI 流水线已集成(集成测试使用 Testcontainers)
- [x] 团队已知晓(2026-03-18 技术分享会)

## 相关决策

- 取代:ADR-017(采用 RabbitMQ 作为消息中间件)
- 关联:ADR-039(事件驱动架构设计规范)

7.4 ADR 的工具支持

管理 ADR 的常用工具:

工具 类型 特点
adr-tools(Nat Pryce) CLI Shell 脚本,支持创建、链接、生成目录
Log4brains Web UI + CLI 自动生成静态网站,支持 Markdown 预览
ADR Manager(VS Code) IDE 插件 在 VS Code 中创建和管理 ADR
Backstage ADR Plugin 平台插件 集成到 Backstage 开发者门户

使用 adr-tools 的基本操作:

# 安装 adr-tools
brew install adr-tools

# 初始化 ADR 目录
adr init docs/architecture/decisions

# 创建新的 ADR
adr new "采用 Kafka 作为核心事件总线"

# 记录取代关系
adr new -s 17 "采用 Kafka 替换 RabbitMQ"

# 生成 ADR 目录索引
adr generate toc > docs/architecture/decisions/README.md

# 生成 ADR 关系图
adr generate graph | dot -Tpng -o adr-graph.png

八、适应度函数的进阶模式

8.1 组合适应度函数

在实际系统中,单一的适应度函数往往不够用。需要将多个函数组合为一个整体评估体系:

package com.example.archunit;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.CompositeArchRule;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.library.Architectures.layeredArchitecture;
import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;

class CompositeArchitectureTest {

    private static JavaClasses classes;

    @BeforeAll
    static void setup() {
        classes = new ClassFileImporter()
                .importPackages("com.example.myapp");
    }

    @Test
    void 系统架构整体适应度评估() {
        ArchRule layerRule = layeredArchitecture()
                .consideringAllDependencies()
                .layer("Controller").definedBy("..controller..")
                .layer("Service").definedBy("..service..")
                .layer("Domain").definedBy("..domain..")
                .layer("Infrastructure").definedBy("..infrastructure..")
                .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
                .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
                .whereLayer("Domain").mayOnlyBeAccessedByLayers("Controller", "Service");

        ArchRule noCyclesRule = slices()
                .matching("com.example.myapp.(*)..")
                .should().beFreeOfCycles();

        ArchRule noInfraInDomain = noClasses()
                .that().resideInAPackage("..domain..")
                .should().dependOnClassesThat()
                .resideInAPackage("..infrastructure..");

        CompositeArchRule compositeRule = CompositeArchRule
                .of(layerRule)
                .and(noCyclesRule)
                .and(noInfraInDomain);

        compositeRule.check(classes);
    }
}

8.2 运行时适应度函数

并非所有架构约束都能在编译时检查。运行时适应度函数用于在生产环境中持续监测架构健康度:

package com.example.fitness;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class RuntimeFitnessFunction {

    private final MeterRegistry meterRegistry;
    private static final double P99_LATENCY_THRESHOLD_MS = 200.0;
    private static final int MAX_DB_CONNECTION_POOL_USAGE_PERCENT = 80;

    public RuntimeFitnessFunction(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    @Scheduled(fixedRate = 60000)
    public void checkApiLatencyFitness() {
        Timer timer = meterRegistry.find("http.server.requests")
                .tag("uri", "/api/**")
                .timer();

        if (timer != null) {
            double p99Ms = timer.percentile(0.99)
                    .toMilliseconds();

            meterRegistry.gauge(
                    "architecture.fitness.api_latency",
                    p99Ms <= P99_LATENCY_THRESHOLD_MS ? 1.0 : 0.0
            );

            if (p99Ms > P99_LATENCY_THRESHOLD_MS) {
                alertTeam(String.format(
                        "API P99 延迟 %.1fms 超过阈值 %.1fms",
                        p99Ms, P99_LATENCY_THRESHOLD_MS
                ));
            }
        }
    }

    @Scheduled(fixedRate = 30000)
    public void checkDatabaseConnectionFitness() {
        double activeConnections = meterRegistry.find("hikaricp.connections.active")
                .gauge().value();
        double maxConnections = meterRegistry.find("hikaricp.connections.max")
                .gauge().value();

        double usagePercent = (activeConnections / maxConnections) * 100;

        meterRegistry.gauge(
                "architecture.fitness.db_pool_usage",
                usagePercent <= MAX_DB_CONNECTION_POOL_USAGE_PERCENT
                        ? 1.0 : 0.0
        );
    }

    private void alertTeam(String message) {
        // 发送到告警通道(Slack、PagerDuty 等)
    }
}

8.3 渐进式收敛策略

对于存量系统,不可能一步到位消除所有架构违规。渐进式收敛策略允许设置逐步收紧的阈值:

package com.example.archunit;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.library.freeze.FreezingArchRule;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;

class GradualConvergenceTest {

    private static JavaClasses classes;

    @BeforeAll
    static void setup() {
        classes = new ClassFileImporter()
                .importPackages("com.example.myapp");
    }

    @Test
    void 冻结现有违规_只阻断新增违规() {
        ArchRule rule = noClasses()
                .that().resideInAPackage("..domain..")
                .should().dependOnClassesThat()
                .resideInAPackage("..controller..");

        // FreezingArchRule 会记录当前违规基线
        // 后续只有新增违规才会导致测试失败
        FreezingArchRule.freeze(rule).check(classes);
    }
}

冻结规则的工作原理:

  1. 首次运行时,记录所有现存违规到 archunit_store 目录。
  2. 后续运行时,只有不在基线中的新增违规才会导致测试失败。
  3. 当开发者修复了某个已知违规,基线自动更新(违规数只减不增)。
  4. 定期审查基线文件,逐步清理历史违规。

九、架构治理仪表盘与度量体系

9.1 关键度量指标

构建架构治理仪表盘需要采集以下核心指标:

指标类别 具体指标 采集方式 目标范围
结构耦合 模块间耦合度(Afferent/Efferent Coupling) JDepend、SonarQube Ca < 20,Ce < 20
结构耦合 循环依赖数 ArchUnit、dependency-cruiser 0
结构耦合 不稳定度(Instability) JDepend 0.3-0.7(避免极端值)
分层合规 层间违规依赖数 ArchUnit 0(新增)
分层合规 Paved Road 覆盖率 自定义扫描 > 90%
决策治理 ADR 覆盖率(重大决策有记录的比例) ADR 工具统计 > 80%
决策治理 过期 ADR 比例 定期审查 < 10%
运行时健康 P99 延迟达标率 Prometheus/Grafana > 99%
运行时健康 错误率 日志分析 < 0.1%

9.2 Grafana 仪表盘配置

{
  "dashboard": {
    "title": "架构治理仪表盘",
    "panels": [
      {
        "title": "适应度函数通过率",
        "type": "gauge",
        "targets": [
          {
            "expr": "sum(architecture_fitness_passed) / sum(architecture_fitness_total) * 100",
            "legendFormat": "通过率"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "thresholds": {
              "steps": [
                { "value": 0, "color": "red" },
                { "value": 80, "color": "yellow" },
                { "value": 95, "color": "green" }
              ]
            },
            "max": 100,
            "unit": "percent"
          }
        }
      },
      {
        "title": "架构违规趋势",
        "type": "timeseries",
        "targets": [
          {
            "expr": "architecture_violations_total",
            "legendFormat": "{{violation_type}}"
          }
        ]
      },
      {
        "title": "模块耦合度热力图",
        "type": "heatmap",
        "targets": [
          {
            "expr": "module_coupling_score",
            "legendFormat": "{{source_module}} -> {{target_module}}"
          }
        ]
      }
    ]
  }
}

9.3 度量数据采集脚本

#!/usr/bin/env python3
"""架构度量指标采集脚本"""

import json
import subprocess
import xml.etree.ElementTree as ET
from dataclasses import dataclass, asdict
from datetime import datetime
from pathlib import Path


@dataclass
class ArchitectureMetrics:
    """架构度量指标数据模型"""
    timestamp: str
    commit_sha: str
    cyclic_dependencies: int
    layer_violations: int
    coupling_score: float
    instability_score: float
    adr_count: int
    fitness_functions_count: int
    fitness_functions_passed: int

    @property
    def fitness_pass_rate(self) -> float:
        if self.fitness_functions_count == 0:
            return 0.0
        return self.fitness_functions_passed / self.fitness_functions_count


def collect_jdepend_metrics(report_path: str) -> dict:
    """从 JDepend 报告中提取耦合度指标"""
    tree = ET.parse(report_path)
    root = tree.getroot()

    total_ca = 0
    total_ce = 0
    package_count = 0
    cycles = 0

    for package in root.findall(".//Package"):
        stats = package.find("Stats")
        if stats is not None:
            total_ca += int(stats.findtext("Ca", "0"))
            total_ce += int(stats.findtext("Ce", "0"))
            package_count += 1

            if stats.findtext("CyclicDeps", "false") == "true":
                cycles += 1

    avg_coupling = (total_ca + total_ce) / max(package_count, 1)
    instability = total_ce / max(total_ca + total_ce, 1)

    return {
        "coupling_score": round(avg_coupling, 2),
        "instability_score": round(instability, 2),
        "cyclic_dependencies": cycles
    }


def count_adrs(adr_dir: str) -> int:
    """统计 ADR 文件数量"""
    adr_path = Path(adr_dir)
    if not adr_path.exists():
        return 0
    return len(list(adr_path.glob("*.md"))) - 1  # 减去 README


def parse_test_results(report_path: str) -> dict:
    """解析架构测试结果"""
    tree = ET.parse(report_path)
    root = tree.getroot()

    total = int(root.attrib.get("tests", "0"))
    failures = int(root.attrib.get("failures", "0"))
    errors = int(root.attrib.get("errors", "0"))

    return {
        "fitness_functions_count": total,
        "fitness_functions_passed": total - failures - errors
    }


def get_commit_sha() -> str:
    """获取当前 Git 提交 SHA"""
    result = subprocess.run(
        ["git", "rev-parse", "HEAD"],
        capture_output=True, text=True
    )
    return result.stdout.strip()[:8]


def main():
    jdepend = collect_jdepend_metrics("build/reports/jdepend/jdepend-report.xml")
    tests = parse_test_results("build/reports/tests/archTest/TEST-results.xml")

    metrics = ArchitectureMetrics(
        timestamp=datetime.utcnow().isoformat(),
        commit_sha=get_commit_sha(),
        cyclic_dependencies=jdepend["cyclic_dependencies"],
        layer_violations=0,
        coupling_score=jdepend["coupling_score"],
        instability_score=jdepend["instability_score"],
        adr_count=count_adrs("docs/architecture/decisions"),
        fitness_functions_count=tests["fitness_functions_count"],
        fitness_functions_passed=tests["fitness_functions_passed"],
    )

    output_path = Path("build/arch-metrics.json")
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(json.dumps(asdict(metrics), indent=2))

    print(f"架构度量采集完成:通过率 {metrics.fitness_pass_rate:.1%}")
    print(f"循环依赖:{metrics.cyclic_dependencies}")
    print(f"耦合度评分:{metrics.coupling_score}")


if __name__ == "__main__":
    main()

十、架构治理的组织保障

10.1 架构治理委员会

自动化工具只是治理的一环,组织层面的保障同样不可或缺。一个高效的架构治理委员会应具备以下特征:

10.2 架构师的角色转变

在演进式架构治理模式下,架构师的角色从「守门人」转变为「园丁」:

传统角色 演进式角色
设计蓝图并监督执行 设定方向并提供引导
审批每一个技术选型 维护 Paved Road 和适应度函数
编写并维护架构文档 推动 ADR 实践并参与编写
在评审会上否决不合规设计 通过自动化工具提供即时反馈
关注「是否正确」 关注「如何演进」

10.3 团队赋能

架构治理不应成为少数架构师的专属领地。通过以下措施赋能全团队参与:

  1. 架构 Kata(Architecture Kata):定期组织架构设计练习,培养团队的架构意识。
  2. 适应度函数共建:让开发者参与编写和维护适应度函数,而非仅由架构师制定。
  3. ADR 民主化:任何团队成员都可以提出 ADR,经评审后纳入决策链。
  4. 技术雷达:定期发布团队技术雷达,让所有人了解技术选型的推荐状态(采纳、试验、评估、暂缓)。

十一、治理模式的权衡与选型

11.1 治理强度谱系

架构治理不是非黑即白的选择,而是一个从「完全自由」到「严格管控」的连续谱系:

治理级别 描述 适用场景 代表实践
L0 无治理 无架构约束,完全自由 原型验证、黑客松
L1 文档治理 有架构文档但不强制执行 小型团队(< 10 人) 架构文档、Wiki
L2 评审治理 定期架构评审、代码审查 中型团队(10-50 人) 评审会、PR Review
L3 自动化治理 CI 中嵌入适应度函数 大型团队(50-200 人) ArchUnit、ADR
L4 平台化治理 平台强制约束、Paved Road 超大型团队(> 200 人) Netflix Paved Road

11.2 治理工具选型决策树

graph TD
    A{团队规模?} -->|< 10 人| B[L1:文档 + Wiki]
    A -->|10-50 人| C{技术栈统一?}
    A -->|> 50 人| D{有平台团队?}
    
    C -->|是| E[L3:ArchUnit + ADR]
    C -->|否| F[L2:评审 + 规范]
    
    D -->|是| G[L4:Paved Road + 平台化]
    D -->|否| H[L3:适应度函数 + CI 集成]
    
    B --> I[推荐工具:Confluence + adr-tools]
    E --> J[推荐工具:ArchUnit + Log4brains + GitHub Actions]
    F --> K[推荐工具:SonarQube + PR 模板]
    G --> L[推荐工具:内部平台 + Backstage + 自定义扫描]
    H --> M[推荐工具:ArchUnit + dependency-cruiser + Grafana]

11.3 常见反模式

在实施架构治理时,需要警惕以下反模式:

  1. 过度治理(Over-governance):规则过多过细,导致开发者花大量时间满足合规要求而非交付业务价值。典型表现是 CI 流水线中有超过 50 条架构规则,且经常误报。
  2. 治理孤岛(Governance Silo):架构治理仅由架构组推动,开发团队被动接受,缺乏认同感。
  3. 僵化治理(Rigid Governance):规则一旦制定就不再调整,无法适应业务变化。例如禁止所有跨模块调用,连合理的需求都无法满足。
  4. 指标膜拜(Metric Worship):过度关注指标数值而忽视实际架构质量。高通过率可能只是因为适应度函数覆盖范围太窄。
  5. 选择性执行(Selective Enforcement):对某些团队或项目网开一面,破坏治理的公平性和权威性。

十二、实战:从零构建架构治理体系

12.1 实施路线图

以下是一个经过验证的渐进式实施路线图,适用于中大型团队:

第一阶段:基础建设(第 1-4 周)

  1. 建立 ADR 实践:创建 ADR 目录结构,用 adr-tools 初始化。
  2. 梳理现有架构约束:与团队一起识别核心架构原则。
  3. 编写首批 3-5 条 ArchUnit 规则:聚焦最关键的分层约束和循环依赖检测。
  4. 集成到 CI 流水线:使用 FreezingArchRule 冻结现有违规。

第二阶段:扩展覆盖(第 5-8 周)

  1. 增加适应度函数覆盖面:扩展到命名规则、注解规范、模块边界等。
  2. 引入运行时适应度函数:添加 P99 延迟、错误率等运行时监控。
  3. 搭建治理仪表盘:用 Grafana 展示关键治理指标。
  4. 定期审查 ADR:确保重大技术决策都有对应 ADR。

第三阶段:深化治理(第 9-12 周)

  1. 逐步收紧冻结基线:每个迭代修复部分历史违规。
  2. 推广到前端项目:引入 dependency-cruiser。
  3. 建立架构治理委员会:定期审查治理指标和架构风险。
  4. 编写团队技术雷达。

第四阶段:平台化(第 13 周+)

  1. 构建项目脚手架:参考 Netflix Paved Road,提供标准化的项目模板。
  2. 开发自定义治理规则:根据业务特点编写领域特定的适应度函数。
  3. 建立治理反馈闭环:将治理数据与研发效能指标关联分析。

12.2 完整项目结构示例

my-project/
├── src/
│   ├── main/java/com/example/myapp/
│   │   ├── controller/
│   │   ├── service/
│   │   ├── domain/
│   │   └── infrastructure/
│   ├── test/java/com/example/myapp/
│   │   └── ... (业务单元测试)
│   └── arch-test/java/com/example/archunit/
│       ├── LayeredArchitectureTest.java
│       ├── PackageDependencyTest.java
│       ├── NamingConventionTest.java
│       ├── CyclicDependencyTest.java
│       └── CompositeArchitectureTest.java
├── docs/
│   └── architecture/
│       └── decisions/
│           ├── 0001-record-architecture-decisions.md
│           ├── 0002-use-layered-architecture.md
│           ├── 0042-adopt-kafka-as-event-bus.md
│           └── README.md
├── scripts/
│   └── collect_arch_metrics.py
├── .github/
│   └── workflows/
│       └── architecture-governance.yml
├── .dependency-cruiser.js
├── build.gradle
└── archunit_store/
    └── ... (冻结基线文件)

12.3 Quick Start 检查清单

## 架构治理 Quick Start 检查清单

### 基础设施
- [ ] 添加 ArchUnit 依赖到 build.gradle/pom.xml
- [ ] 创建 src/arch-test 源码目录
- [ ] 配置独立的 archTest Gradle 任务
- [ ] 在 CI 流水线中添加架构测试步骤

### 核心规则
- [ ] 分层架构约束(层间依赖方向)
- [ ] 循环依赖检测(模块间、包间)
- [ ] 命名规约检查(Controller、Service、Repository)
- [ ] 领域层纯净性(不依赖基础设施和 UI)

### ADR 实践
- [ ] 初始化 ADR 目录(adr init)
- [ ] 编写第一条 ADR(记录架构决策实践本身)
- [ ] 在 PR 模板中添加 ADR 提示
- [ ] 将 ADR 纳入新人培训材料

### 度量与可视化
- [ ] 配置 JDepend 或 SonarQube 采集耦合度
- [ ] 搭建 Grafana 治理仪表盘
- [ ] 设置架构违规趋势告警

### 组织保障
- [ ] 成立架构治理工作组
- [ ] 安排首次架构 Kata 工作坊
- [ ] 发布团队架构原则文档

十三、总结

架构治理的本质是在「变化」中守住「不变」。传统的文档和评审手段在面对大规模系统和高速迭代时力不从心,自动化的适应度函数提供了一条可度量、可持续、可演进的治理路径。

核心要点回顾:

  1. 适应度函数是架构约束的可执行表达,应当像单元测试一样融入日常开发流程。ArchUnit、NetArchTest、dependency-cruiser 等工具让这一理念在各技术栈中得以落地。
  2. Netflix 的 Paved Road 策略证明了「引导优于管控」的治理哲学——让正确的做法成为最简单的选择,比强制禁止更有效。
  3. 架构决策记录(ADR) 解决了决策上下文丢失的问题,让架构演进具备可追溯性。
  4. 渐进式收敛是治理落地的关键策略——用 FreezingArchRule 冻结存量违规,只阻断增量,逐步收紧。
  5. 度量驱动的治理仪表盘让架构健康度从主观感受变为客观数据。
  6. 组织保障与技术手段同样重要——架构治理委员会、团队赋能、架构 Kata 缺一不可。

架构治理不是一次性项目,而是持续运营的实践。从最关键的三五条规则开始,在 CI 中跑起来,再逐步扩展——这就是演进式架构治理的精髓。


上一篇:技术债务 下一篇:遗留系统现代化

参考资料

  1. Neal Ford, Rebecca Parsons, Patrick Kua.《Building Evolutionary Architectures: Automated Software Governance》. O’Reilly Media, 2023(第二版).
  2. Michael Nygard. “Documenting Architecture Decisions”. Cognitect Blog, 2011.
  3. Netflix Technology Blog. “Ready for changes with Hexagonal Architecture”. 2020.
  4. Netflix Technology Blog. “Paved Road at Netflix”. 2018.
  5. Peter Gafert. ArchUnit 官方文档. https://www.archunit.org/
  6. Ben Morris. NetArchTest 官方仓库. https://github.com/BenMorris/NetArchTest
  7. dependency-cruiser 官方文档. https://github.com/sverweij/dependency-cruiser
  8. ThoughtWorks Technology Radar. “Architecture Decision Records”. 2016-2024.
  9. Gartner. “Technical Debt: A Strategic Challenge for IT Leaders”. 2024.
  10. Martin Fowler. “Fitness Function-Driven Development”. martinfowler.com, 2023.
  11. Log4brains 官方文档. https://github.com/thomvaill/log4brains
  12. Nat Pryce. adr-tools 官方仓库. https://github.com/npryce/adr-tools

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-13 · architecture

【系统架构设计百科】架构决策与 ADR:如何做出可追溯的技术决策

口头约定的架构决策会在人员流动中丢失,会在争论中反复翻车。ADR(Architecture Decision Records)用一种轻量的文档格式,把每一个关键技术决策的背景、选项、理由和代价写下来,跟着代码一起版本管理。本文从 ADR 的三种主流格式讲到 Git 仓库中的实操管理,再拆解 Spotify 和 Uber 的工业实践。

2026-04-13 · architecture

【系统架构设计百科】架构视图与文档:C4 模型从入门到实战

架构图画完三个月就过期,架构文档写完没人看。问题不在于画不画,而在于用什么模型画、用什么方式维护。本文从 C4 模型的四层视图出发,拆解 diagram-as-code 工具链和文档即代码的工程实践,给出一套让架构文档能活下来的方法。

2026-04-13 · architecture

【系统架构设计百科】架构质量属性:不只是"高可用高性能"

需求评审时写下的'高可用、高性能、高并发',到了架构设计阶段几乎无法落地——因为它们不是可执行的需求。本文从 SEI/CMU 的质量属性理论出发,用 stimulus-response 场景模型把模糊需求变成可量化、可验证的架构约束,并拆解属性之间的冲突与联动关系。

2026-04-13 · architecture

【系统架构设计百科】告警策略:如何避免"狼来了"

大多数团队的告警系统都在制造噪声而不是传递信号。阈值告警看似直观,实则产生大量误报和漏报,值班工程师在凌晨三点被叫醒,却发现只是一次无害的毛刺。本文从告警疲劳的工业数据出发,拆解基于 SLO 的多窗口燃烧率告警算法,深入 Alertmanager 的路由、抑制与分组机制,结合 PagerDuty 的告警疲劳研究和真实工程案例,给出一套可落地的告警策略设计方法。


By .