公共组件不是 common 包:从依赖治理到公共架构框架

前言:本文章重点分享如何从通用组件、公共组件、公共架构框架三个层次,把可复用能力、工程质量、安全合规、数据治理和研发效率沉淀为平台化默认路径。

很多团队都会经历同一个阶段:几个服务里反复出现 TraceId工具、审计日志、字段加密、接口限流、配置刷新、Kafka 发送、MyBatis 拦截器,于是大家决定抽一个 common 包。

刚开始效果很好。重复代码少了,新服务接入也快了。

但随着能力变多,问题也会出现:

  • 一个服务只想用 Trace,却被迫引入 Redis、Kafka、MyBatis、KMS。
  • common-util 里开始读取 Spring Environment、启动线程池、访问外部系统。
  • 公共 API 暴露了内部实现类,业务服务一旦引用就无法替换。
  • AutoConfiguration 里堆满 if/else,装配层变成新的业务逻辑层。
  • 审计、加密、脱敏、限流的失败模式不清楚,线上出了问题没人敢升级。
  • 数据库表、迁移脚本、慢 SQL、归档策略没有纳入组件治理。

所以,公共组件建设的目标不是“把代码放到一个仓库”,而是把稳定、跨服务、可治理的工程能力产品化。

grace-spring-common为例,github地址,它承担公共组件层职责,为多个 Java 应用、网关和控制面提供共享能力。它不是一个单体 starter,而是由 grace-common-apigrace-common-utilgrace-spring-parentgrace-spring-starter-* 等模块共同组成,并封装 trace、cache、audit、encryption、desensitization、limitbreak、monitor、notification、config、registry、job、kafka、mybatis-plus 等横切能力。

这篇文章要回答的是:什么能力适合进入公共组件,如何控制依赖边界,如何设计 Starter,如何处理数据治理和失败模式,以及如何把组件从“能用”运营到“值得长期依赖”。

一、为什么 common 包会失控

很多 common 包失控,不是因为一开始设计得太差,而是因为边界没有持续治理。

最常见的演化路径是:

重复代码
  → 抽工具类
  → 工具类变多
  → 加入注解和模型
  → 访问配置和外部系统
  → 引入 Spring / Redis / Kafka / MyBatis
  → 所有服务被迫依赖一个越来越重的 common 包

问题的根源在于:大家把“复用”当成唯一目标,却忽略了公共能力的运行时边界、依赖成本、兼容性成本和组织维护成本。

公共组件不是“大家都可能用的代码”,而是多个服务稳定依赖、具备明确 owner、明确版本策略、明确失败模式的技术能力单元。

一个公共组件至少要回答这些问题:

  1. 它解决的是稳定横切问题,还是临时业务复用?
  2. 它的默认行为是否安全可靠?
  3. 它是否可以关闭、替换、扩展?
  4. 它失败时是阻断、降级,还是只记录?
  5. 它是否有日志、指标、trace、health 状态?
  6. 它是否有升级说明、迁移路径和 owner?

如果这些问题回答不清楚,就不要急着把代码放进公共层。

二、三层概念边界:通用组件、公共组件、公共架构框架

讨论公共组件前,先要区分三个概念。

层次 解决的问题 典型形态 关键要求
通用组件 局部技术复用 工具类、基础模型、轻量 SDK、通用注解 低依赖、低侵入、API 清晰
公共组件 跨服务横切能力 Trace、Audit、Encryption、RateLimit、Monitor Starter 边界稳定、可配置、可替换、可观测
公共架构框架 组织级工程一致性 BOM、Parent、Starter 规范、配置规范、安全合规默认能力、发布治理 默认正确、可扩展、可运营

通用组件更偏“可复用功能”。例如字符串工具、编码工具、基础响应模型、错误码模型、轻量校验器。它们不应该绑定 Spring 生命周期,也不应该访问 Redis、Kafka、DataSource 这类外部系统。

公共组件更偏“跨服务标准能力”。例如审计日志、链路追踪、字段加密、数据脱敏、限流熔断、指标采集、统一通知。这些能力一旦被多个服务接入,就必须有 owner、版本策略、失败模式和兼容性承诺。

公共架构框架则更高一层。它不是某一个 jar,而是一套默认工程路径:服务如何启动,如何接入配置,如何暴露指标,如何记录审计,如何处理异常,如何管理依赖,如何升级版本。

三者关系可以理解为:

公共架构框架
  ├── 依赖治理:BOM / parent / 版本矩阵
  ├── Starter 规范:条件装配 / 默认 Bean / 扩展点 / 开关
  ├── 安全合规:审计 / 加密 / 脱敏 / 防重放 / 适当性
  ├── 可观测性:trace / metrics / logs / health
  └── 公共组件:audit / trace / encrypt / ratelimit / monitor / kafka
        └── 通用组件:annotation / model / util / contract / policy

公共组件最怕的不是抽象不够,而是把这三层混成一个大 common 包。短期看接入方便,长期看会变成所有服务都绕不开、没人敢改、没人敢升级的中心化技术债。

三、公共组件准入与反面清单

公共组件要服务稳定边界,不服务临时复用。一段代码是否适合进入公共组件,可以先问六个问题:

  1. 它是否跨多个服务长期复用?
  2. 它是否表达稳定技术能力,而不是某个业务流程?
  3. 它是否可以通过接口、配置、注解或策略适配差异?
  4. 它是否值得承担兼容性、测试、发布和支持成本?
  5. 它是否有明确 owner 负责路线图、版本和问题响应?
  6. 它是否能通过指标、日志、健康检查被观察和治理?

如果答案是否定的,不要急着抽公共组件。业务代码重复两三次并不可怕,可怕的是把尚未稳定的业务概念提前固化到公共包中。

适合进入公共组件的能力

能力 解决的问题 公共化原因
Trace 统一链路标识,串联日志、审计、指标 所有服务都需要统一排障上下文
Audit 关键写操作留痕,支撑审计留存 合规要求一致,不能每个服务各写一套
Encryption PII 字段加密存储 数据安全底线必须统一
Desensitization 手机号、证件号、持仓等展示脱敏 输出安全和权限策略需要一致
RateLimit / CircuitBreaker 入口流量保护和依赖故障隔离 稳定性策略需要统一治理
Monitor 慢查询、Kafka lag、fallback rate 可观测 指标口径要统一,便于平台监控
Notification 告警和业务触达的多通道降级 通知链路需要统一兜底和追踪
Config / Gray 配置热刷新与灰度开关治理 灰度决策和配置变更需要可控

不应该进入公共组件的内容

反面清单同样重要。

不应进入 原因
具体业务流程 变化频繁,会把公共组件拖入业务迭代节奏
特定服务的 DTO / VO / Query 会污染公共 API,导致跨服务耦合
特定业务状态机 通常属于业务域,不属于技术域
只被一个服务使用的逻辑 不值得承担公共组件兼容成本
强依赖某张业务表结构的逻辑 数据模型变化会影响所有接入方
AutoConfiguration 中的大段业务分支 装配层会变成隐藏业务层
util 中访问外部系统 util 会失去轻量、纯净、低依赖特征
默认开启但无法关闭的高风险能力 线上故障时无法止血

一个简单判断是:如果这段代码离开当前业务语境就说不清楚含义,它大概率不该进入公共组件。

四、依赖治理与 Starter 边界

依赖治理是公共组件最重要、也最容易被低估的部分。公共组件一旦被多个服务引用,它的依赖会被传递到所有服务中。一个错误依赖可能导致类冲突、版本冲突、启动失败,甚至安全漏洞扩散。

对架构师来说,公共框架的依赖设计不是“能编译就行”,而是要对每个 POM 都了如指掌:哪个模块能依赖谁,哪个依赖只是编译期需要,哪个依赖会传递到业务服务,哪个依赖可能引入 Spring、Redis、Kafka、MyBatis、KMS 这类运行时能力,都要清楚。

一个好的公共框架应该优先从底层出发做最小依赖设计:先定义最轻的 API 和 contract,再定义纯净 util,再定义独立 starter,最后才做场景化聚合。这样更不容易出现循环依赖、分层倒挂和“为了一个小能力引入半个平台”的问题。

另一个架构师工作原则是“规模放大思维”(Scaling Mindset)。如果你不确定某个设计好不好,不妨想象它被重复 100 遍:100 个服务都接入这个 starter,100 个接口都使用这个注解,100 个模块都依赖这个 POM,100 次升级都要兼容这个配置。放大之后仍然简单、稳定、可解释、可迁移的设计,才更接近最佳实践。

推荐依赖方向如下:

grace-common-api
  ↑
grace-common-util
  ↑
grace-spring-starter-xxx
  ↑
business-service

模块依赖关系

组件框架工程的实际设计:

grace-spring-common
  ├─ build-tools(CI 脚本与配置,无依赖)
  ├─ grace-common-api(零依赖通用 API)
  ├─ grace-common-util(依赖 grace-common-api,无 Spring)
  ├─ grace-spring-parent(BOM 版本仲裁)
  └─ grace-spring-starter
       ├─ grace-spring-starter-api(依赖 grace-common-api + grace-common-util + spring-web,聚合 i18n/错误码/异常处理)
       ├─ grace-spring-starter-security(依赖 api + springcloud + redisson)
       ├─ grace-spring-starter-audit(依赖 api + trace + kafka)
       ├─ grace-spring-starter-desensitization(依赖 api)
       ├─ grace-spring-starter-encryption(依赖 api + redis)
       ├─ grace-spring-starter-limitbreak(依赖 api + springcloud + resilience4j)
       ├─ grace-spring-starter-cache(依赖 api)
       ├─ grace-spring-starter-trace(依赖 api)
       ├─ grace-spring-starter-config(依赖 api + cache)
       ├─ grace-spring-starter-springcloud(依赖 api)
       ├─ grace-spring-starter-mybatis-plus(依赖 api + encryption + desensitization)
       ├─ grace-spring-starter-registry(依赖 api + springcloud)
       ├─ grace-spring-starter-jobadmin(依赖 api + registry + jobhandler)
       ├─ grace-spring-starter-jobhandler(依赖 api + registry)
       ├─ grace-spring-starter-kafka(依赖 api)
       └─ grace-spring-starter-svc-tiny(面向Java业务服务的最小聚合包:api + security + trace + config + monitor + springcloud + cache + mybatis-plus + registry + limitbreak + audit + kafka)

API 包保持轻量

grace-common-api 适合放注解、枚举、常量、基础响应模型和通用异常,例如 @Encrypt@DesensitizeBaseResponseErrorCode

它应该只依赖 JDK 或极少量稳定标准依赖,不能依赖 Spring、Redis、Kafka、MyBatis 或 Web 容器。

原因很简单:API 包经常会被 DTO、Entity、DAO、Domain 模块引用。如果 API 包依赖 Spring Boot starter,就会把运行时框架污染到纯模型层。

util 包不要变成万能入口

grace-common-util 可以放 JSON、集合、字符串、校验、文件、编码、基础哈希等工具,但不能读取 Spring Environment,不能启动线程,不能访问 Redis、Kafka、DataSource。

如果一个“工具类”需要外部系统、线程池、连接池或 Spring 生命周期,它就不是 util,而应该进入 starter 或 infrastructure adapter。

Starter 控制依赖扩散

一个优秀 Starter 应该“少侵入、可替换、可观测”。典型写法如下:

@AutoConfiguration
@EnableConfigurationProperties(CacheProperties.class)
@ConditionalOnProperty(prefix = "grace.cache", name = "enabled", havingValue = "true", matchIfMissing = true)
public class CacheAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public SingleFlightCache singleFlightCache() {
        return new SingleFlightCache();
    }
}

这段配置体现了三个关键点:

  • enabled 开关保证能力可关闭。
  • matchIfMissing = true 降低默认接入成本。
  • @ConditionalOnMissingBean 给业务侧替换默认实现留下空间。

Spring Boot 3 场景下,Starter 还应该显式遵守这些契约:

契约 说明
自动配置注册 使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
配置类 使用 @ConfigurationProperties,并生成 metadata,提供 IDE YAML 提示
装配顺序 @AutoConfigureBefore / @AutoConfigureAfter 表达依赖顺序
可选依赖 @ConditionalOnClass 判断 Redis、Kafka、MyBatis、Micrometer 等是否存在
替换点 @ConditionalOnMissingBean 暴露稳定扩展点
总开关 每个 starter 都提供 grace.xxx.enabled
可观测性 暴露关键 metrics、health indicator、log marker
文档 提供 Maven 坐标、最小 YAML、默认行为、扩展点、失败模式

自动装配层只负责组装对象,不应该写复杂业务逻辑。如果 AutoConfiguration 里出现大量 if/else、流程判断、数据转换,就说明领域逻辑被塞进了装配层,应该下沉到 domain 或 application。

五、改进版 DDD 与端口适配器

DDD 经常被误解为“建很多包”和“写 Entity、ValueObject、Repository”。对公共组件来说,直接照搬业务 DDD 会有问题,因为公共组件不是订单、会员、账户这样的单一业务域,而是一组技术域和平台域。

更适合公共组件的是“改进版 DDD”:保留分层、边界、依赖方向和领域语言,但不机械套用业务聚合模型。

推荐结构如下:

starter-xxx
  ├── api
  │   ├── annotation
  │   ├── model
  │   └── contract
  ├── domain
  │   ├── model
  │   ├── service
  │   ├── policy
  │   └── event
  ├── application
  │   ├── usecase
  │   └── orchestrator
  ├── infrastructure
  │   ├── adapter
  │   ├── client
  │   ├── persistence
  │   └── messaging
  ├── autoconfigure
  │   ├── XxxAutoConfiguration
  │   └── XxxProperties
  └── support
      └── internal utilities

这不是要求所有 Starter 都建满这些包,而是给出边界参考:

职责 不应该做的事
api 暴露注解、接口、枚举、稳定模型 暴露内部实现
domain 表达核心规则、策略、领域事件 依赖 Spring、Redis、Kafka、MyBatis
application 编排用例流程,定义端口调用 直接操作外部中间件
infrastructure 适配 Redis、Kafka、KMS、Micrometer、DataSource 反向污染领域模型
autoconfigure Spring Bean 装配与条件配置 编写复杂业务流程

关键依赖规则是:domain/application 定义端口,infrastructure 实现端口,autoconfigure 负责组装。

公共组件常见扩展点可以分为几类:

扩展点类型 示例 用途
SPI 扩展 KmsProviderAuditLogWriter 替换外部能力实现
Policy 扩展 AuditFailurePolicyRateLimitPolicy 替换策略判断
Adapter 扩展 Kafka、File、DB、HTTP Writer 替换输出通道
Filter / Interceptor 扩展 TraceFilter、SQLAuditInterceptor 接入不同入口
Properties 扩展 grace.audit.*grace.encrypt.* 保持配置稳定,替换内部实现

扩展点必须标明哪些是稳定 API,哪些只是 internal support。否则业务方一旦引用内部类,后续演进会非常困难。

六、完整 Audit Starter 示例:从 5 分钟接入到可替换实现

公共组件最终要被业务团队使用。架构边界再漂亮,如果业务方接入成本高、默认行为不清楚、失败后不知道去哪排查,就很难推广。

以 Audit Starter 为例,一个合格的接入路径应该让业务方 5 分钟完成最小接入。

1. 引入依赖

<dependency>
    <groupId>com.example</groupId>
    <artifactId>grace-spring-starter-audit</artifactId>
</dependency>

版本由 grace-spring-parent 或 BOM 统一管理,业务服务不应该手动散落维护公共组件版本。

2. 最小配置

grace:
  audit:
    enabled: true
    writer: kafka
    fallback-file: /var/log/app/audit-fallback.log
    include-request-body: false

文档必须写清楚默认行为:默认是否开启、默认 writer 是什么、Kafka 不可用时 fallback 到哪里、是否记录请求体、是否自动脱敏。

3. 注解使用

@Audit(action = "ARTICLE_PUBLISH", resourceType = "article")
@PostMapping("/articles/{id}/publish")
public void publish(@PathVariable Long id) {
    articleService.publish(id);
}

注解参数应该少而稳定。不要让 @Audit 变成“大而全配置中心”。复杂策略应通过 AuditFailurePolicyAuditFieldMaskerAuditLogWriter 这类扩展点表达。

4. 领域模型与 Writer SPI

public record AuditEvent(
    String eventId,
    String traceId,
    String operatorId,
    String resourceType,
    String resourceId,
    String action,
    boolean success
) {
}

public interface AuditLogWriter {
    void write(AuditEvent event);
}

AuditEvent 是稳定领域模型,AuditLogWriter 是稳定端口。Kafka、DB、File、HTTP 都只是不同适配器。

5. 自动装配

@AutoConfiguration
@EnableConfigurationProperties(AuditProperties.class)
@ConditionalOnProperty(prefix = "grace.audit", name = "enabled", havingValue = "true", matchIfMissing = true)
public class AuditAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public AuditLogWriter auditLogWriter(AuditProperties properties) {
        return new KafkaAuditLogWriter(properties);
    }

    @Bean
    public AuditLogAspect auditLogAspect(AuditLogWriter writer) {
        return new AuditLogAspect(writer);
    }
}

Spring Boot 3 需要在下面的文件中注册:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

内容类似:

com.example.audit.autoconfigure.AuditAutoConfiguration

如果 Audit 依赖 Trace,需要用 @AutoConfigureAfter(TraceAutoConfiguration.class) 显式表达装配顺序。

6. 业务覆盖默认实现

业务方如果要替换写入方式,只应该覆盖稳定扩展点:

@Bean
public AuditLogWriter auditLogWriter() {
    return new CustomAuditLogWriter();
}

不要让业务继承 KafkaAuditLogWriter 这类内部实现。继承内部实现会把业务方绑定到基础设施细节,后续公共组件重构会很痛苦。

7. 失败策略

Audit 组件必须明确失败模式。

场景 默认策略 原因
Kafka 不可用 写 fallback 文件并告警 不阻断主业务,但保留审计补偿能力
fallback 文件不可写 告警并记录 error metric 避免静默丢审计
审计事件缺少 trace id 自动补齐或记录异常标签 保证可追踪
eventId 重复 幂等忽略或覆盖同一事件 避免 retry 造成重复审计
生产环境关闭审计 启动阻断或强告警 合规能力不应被随意关闭

8. 自动装配测试

class AuditAutoConfigurationTest {

    private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
        .withUserConfiguration(AuditAutoConfiguration.class)
        .withPropertyValues("grace.audit.enabled=true");

    @Test
    void shouldCreateDefaultAuditWriter() {
        contextRunner.run(context -> {
            assertThat(context).hasSingleBean(AuditLogWriter.class);
        });
    }
}

可选依赖可以用 FilteredClassLoader 测试,Kafka、Postgres 等真实依赖可以用 Testcontainers 验证。自动装配测试不是锦上添花,而是 Starter 能长期演进的基本保障。

七、数据治理与数据库边界

公共组件不能只关注 Java 层封装。只要组件涉及数据访问、审计日志、字段加密、慢查询、分区表或迁移脚本,就必须纳入数据治理。

数据访问层边界

公共组件在 DAL 层可以提供框架增强,但不应该承载业务查询模型。

可以提供 不建议提供
MyBatis Plus 自动配置 强绑定业务字段的 BaseEntity
分页插件和执行顺序编排 跨业务通用 Mapper
数据权限 / 租户隔离拦截器 隐式拼接业务查询条件
字段加解密 TypeHandler 自动修改业务 SQL 语义的黑盒逻辑
SQL 审计拦截器 与某张业务表结构强耦合的逻辑
慢 SQL 采集 无法解释的 SQL 改写
连接池指标 启动时静默执行高风险 DDL

所有自动改写 SQL 的能力都要可关闭、可观测、可解释,并能输出最终 SQL 或 SQL 指纹用于审计。

SQL 审计治理闭环

慢查询不应该只记录一行日志,而应该形成治理闭环:采集、告警、归因、整改、豁免。

SQL 审计至少应记录:

  • SQL 指纹。
  • 执行耗时。
  • 扫描行数、返回行数、影响行数。
  • 数据源、库表、调用服务、接口名。
  • trace id、request id、用户或租户信息。
  • 是否全表扫描、是否大分页、是否长事务。
  • explain 采样或离线分析结果。
  • 整改责任人、豁免原因和过期时间。

审计日志表设计

审计日志不是普通业务表。它要支持合规取证、问题追踪和长期留存。

关键设计原则包括:

  1. 审计事件 ID 全局唯一,保证幂等写入。
  2. trace id / span id / request id 与业务日志打通。
  3. 记录操作者、主体类型、资源类型、资源 ID、操作类型、结果、风险等级。
  4. 操作前后快照只存摘要或脱敏后的必要字段,避免审计表二次泄露。
  5. 审计日志默认追加写,不允许业务侧更新或删除。
  6. 支持按时间分区、定期归档、留存策略和合规检索。

字段加密、查询与脱敏边界

字段安全要分层处理。

层面 目标 注意点
落库加密 防止数据库泄露后明文暴露 关注密文长度、key id、轮换、历史兼容
查询支持 支持必要检索 等值查询可用哈希辅助列,模糊查询需专门方案
接口脱敏 控制输出给前端或外部系统的数据 按角色、场景、字段等级脱敏
日志脱敏 防止日志、异常、审计、消息体泄露 覆盖 request、response、stacktrace、fallback 文件
导出脱敏 控制批量数据离线传播 审批、留痕、权限、文件加密

不能让业务方误以为“加一个注解”就同时解决存储、查询、日志、导出和缓存中的全部敏感数据问题。

分区、归档与迁移治理

分区管理组件只应服务框架自身或横切治理表,例如 audit_logsql_audit_logjob_lognotification_log,不应擅自管理业务核心表分区。

分区表治理要明确:

  • 分区键必须与主要查询条件一致。
  • 自动建未来分区,缺失分区要告警。
  • 历史分区支持归档、压缩、只读。
  • 删除过期分区必须走审批或配置白名单。
  • DDL 执行要避开高峰并记录审计。

公共组件升级如果涉及建表、改字段、加索引,必须提供 migration 文件、回滚说明和兼容窗口。禁止组件启动时静默执行高风险 DDL。大表变更建议采用 expand-migrate-contract:先扩展字段,再迁移数据,最后收缩旧结构。

八、配置、失败模式与错误处理矩阵

公共组件必须明确配置边界。每个 Starter 都应该有自己的 Properties,例如 TracePropertiesAuditPropertiesEncryptionPropertiesRateLimitPropertiesMonitorPropertiesNotificationProperties

配置模型要遵守五条原则:

  1. prefix 稳定,例如 grace.auditgrace.encrypt
  2. 默认值安全。
  3. 生产危险配置启动时阻断。
  4. 配置项命名表达业务含义,而不是实现细节。
  5. 热更新配置和需重启配置明确区分。

公共组件还必须明确失败模式,不同能力失败时不能一刀切。

阶段 示例 默认策略 是否阻断 是否告警
启动期 生产环境 KMS provider 缺失 fail-closed
启动期 可选依赖不存在 自动跳过对应 Bean
运行期 Kafka 审计写入失败 fallback 文件
运行期 Redis 限流不可用 本地令牌桶降级
运行期 防重放签名非法 拒绝请求
运行期 指标上报失败 记录日志和 trace 视情况
降级期 fallback 文件持续增长 限速并告警
恢复期 Kafka 恢复 停止 fallback,记录恢复事件

安全、审计、加密这类能力要谨慎选择 fail-open 或 fail-closed。默认值不是技术偏好,而是风险决策。

在量化资讯系统中,公共组件还承载合规约束:

  • N-04:适当性分级授权,基于会员等级和适当性等级控制功能可见性。
  • N-05:审计日志长期留存,关键操作可追踪、可回放。
  • N-08:免责声明统一注入,输入/输出守门员过滤。
  • N-10:敏感数据 AES-256 加密与 PII 分级脱敏。

这些约束不应散落在业务服务中,而应该尽量通过 Starter 的默认能力、注解、过滤器、拦截器和配置校验统一落地。

九、测试、发布与迁移

公共组件测试不能只测工具方法。它至少要覆盖自动装配、配置、扩展点、可选依赖、失败模式、顺序和幂等性。

推荐测试策略:

测试类型 工具 / 方式 验证内容
自动装配测试 ApplicationContextRunner enabled=false、默认 Bean、Bean 覆盖、Properties 默认值
可选依赖测试 FilteredClassLoader Redis/Kafka/MyBatis 缺失时能否正常启动
集成测试 @SpringBootTest 最小服务接入链路,但不要滥用
中间件测试 Testcontainers Redis、Kafka、Postgres 等真实行为
契约测试 自定义断言 幂等、重试、fallback、重复加密、重复审计写入

尤其是写操作相关能力,要重点检查幂等性。例如 Kafka retry 是否造成重复审计、fallback 是否重复写、字段加密是否避免重复加密、分布式锁是否只释放当前线程持有的锁、配置刷新是否可以重复执行。

版本发布建议遵循语义化版本:

版本 含义 示例
PATCH bugfix,不改变接口和行为 修复审计 Kafka 降级逻辑
MINOR 新增兼容能力 新增 jobadmin/jobhandler starter
MAJOR 破坏性变更 Spring Boot 大版本升级

除此之外,还要补充发布规则:

  • 每个版本必须有 changelog、升级说明和风险等级。
  • 配置项废弃先标记 deprecated,再跨版本删除。
  • 默认行为变更必须进入 MAJOR。
  • 破坏性变更必须有迁移指南或迁移工具。
  • 公共 Starter 应建立兼容性测试矩阵,覆盖主要 JDK、Spring Boot 和中间件版本。
  • 标准组件升级应纳入统一发布节奏,避免每个业务团队自行踩坑。

每个 Starter 至少要有接入文档:功能说明、适用场景、Maven 依赖、YAML 配置、最小示例、扩展点、默认行为、失败模式和注意事项。文档越清楚,业务方越不需要阅读源码猜行为。

发布前建议使用下面这张检查清单:

检查项 问题
边界 是否明确适用场景和不适用场景?
依赖 是否存在非必要传递依赖?
开关 是否支持 enabled=false
扩展 是否支持稳定 Bean 替换或 SPI?
配置 是否有稳定 prefix、默认值和 metadata?
失败模式 是否定义启动期、运行期、降级期、恢复期策略?
可观测性 是否有日志、指标、trace、health?
数据风险 是否涉及 SQL、迁移、分区、加密、脱敏?
幂等 retry、fallback、重复执行是否安全?
文档 是否有 Maven、YAML、示例、扩展点、FAQ?
测试 是否覆盖 AutoConfiguration、可选依赖和真实中间件?
owner 是否有负责人、响应机制和版本路线图?
迁移 是否提供升级说明、deprecated 周期和回滚方案?

十、平台化治理与度量体系

公共组件建设是组织级生产力投资,不是技术团队自嗨。它要回答三个问题:

  1. 是否降低了新服务交付成本?
  2. 是否减少了跨团队重复建设?
  3. 是否提升了安全、合规、稳定性和可观测性底线?

成熟度分级

公共组件不应该一上线就强制全团队接入。建议分为四级:

阶段 含义 推广策略
Sandbox 实验能力,仅验证设计 不建议业务依赖
Pilot 试点能力,限定 1-2 个业务接入 收集接入成本和问题
Stable 稳定能力,可推荐接入 提供文档、示例、支持 SLA
Standard 标准能力,新服务默认接入 纳入脚手架、质量门禁和架构规范

Trace、Audit、Encryption、Desensitization、RateLimit 这类基础能力,成熟后应逐步从“推荐接入”升级为“新服务默认接入”。

owner 与评审机制

每个公共组件都必须有 owner。owner 负责:

  • 路线图。
  • API 评审。
  • 版本发布。
  • 线上问题响应。
  • 兼容性承诺。
  • 接入文档。
  • 业务团队反馈闭环。

重大能力进入公共层前,建议通过 RFC 或 ADR 评审,评审内容包括:复用范围、抽象边界、依赖风险、数据风险、默认行为、失败模式、测试覆盖、文档完备度和维护 owner。

准入、准出和迁移

公共组件不仅要有准入标准,也要有准出机制。

当一个组件长期无人维护、使用量极低、能力被替代,或设计已经无法满足新架构时,应进入 deprecated 流程:

  1. 标记 deprecated。
  2. 发布替代方案。
  3. 提供迁移指南。
  4. 给出下线时间表。
  5. 跟踪业务服务迁移进度。
  6. 到期后移除或冻结维护。

没有准出机制,公共组件仓库会不断膨胀,最后变成没人敢删、没人敢动的历史包。

度量指标

没有度量,公共组件就很难证明平台化价值。

指标 说明
接入覆盖率 多少服务接入 Trace、Audit、Encryption、RateLimit
接入成本 新服务接入耗时、配置项数量、业务代码改动行数
质量收益 相关线上故障数、重复缺陷减少量
安全合规收益 敏感数据加密覆盖率、审计日志完整率
维护效率 版本升级成功率、破坏性变更次数、平均问题响应时间
运行健康度 fallback rate、Kafka lag、慢 SQL 数、连接池耗尽次数
推广效果 Pilot 到 Stable 的转化率、业务团队反馈响应时间

公共组件要像平台产品一样运营,周期性输出健康度报告,而不是只在仓库里维护代码。

最后总结三个关键收获:

  1. 公共组件不是 common 包,而是把跨服务标准能力产品化,把安全、合规、可观测和稳定性变成默认路径。
  2. 好的公共组件必须同时具备技术边界和治理边界:依赖可控、Starter 可替换、失败可预期、数据风险可审计、版本可演进。
  3. 公共架构框架的目标不是封装一切,而是降低系统整体复杂度;如果缺少 owner、测试、度量和迁移机制,公共组件本身也会变成新的技术债。
posted @ 2026-06-23 10:11  鱼007  阅读(21)  评论(0)    收藏  举报