公共组件不是 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-api、grace-common-util、grace-spring-parent、grace-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、明确版本策略、明确失败模式的技术能力单元。
一个公共组件至少要回答这些问题:
- 它解决的是稳定横切问题,还是临时业务复用?
- 它的默认行为是否安全可靠?
- 它是否可以关闭、替换、扩展?
- 它失败时是阻断、降级,还是只记录?
- 它是否有日志、指标、trace、health 状态?
- 它是否有升级说明、迁移路径和 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 包。短期看接入方便,长期看会变成所有服务都绕不开、没人敢改、没人敢升级的中心化技术债。
三、公共组件准入与反面清单
公共组件要服务稳定边界,不服务临时复用。一段代码是否适合进入公共组件,可以先问六个问题:
- 它是否跨多个服务长期复用?
- 它是否表达稳定技术能力,而不是某个业务流程?
- 它是否可以通过接口、配置、注解或策略适配差异?
- 它是否值得承担兼容性、测试、发布和支持成本?
- 它是否有明确 owner 负责路线图、版本和问题响应?
- 它是否能通过指标、日志、健康检查被观察和治理?
如果答案是否定的,不要急着抽公共组件。业务代码重复两三次并不可怕,可怕的是把尚未稳定的业务概念提前固化到公共包中。
适合进入公共组件的能力
| 能力 | 解决的问题 | 公共化原因 |
|---|---|---|
| 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、@Desensitize、BaseResponse、ErrorCode。
它应该只依赖 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 扩展 | KmsProvider、AuditLogWriter |
替换外部能力实现 |
| Policy 扩展 | AuditFailurePolicy、RateLimitPolicy |
替换策略判断 |
| 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 变成“大而全配置中心”。复杂策略应通过 AuditFailurePolicy、AuditFieldMasker、AuditLogWriter 这类扩展点表达。
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 采样或离线分析结果。
- 整改责任人、豁免原因和过期时间。
审计日志表设计
审计日志不是普通业务表。它要支持合规取证、问题追踪和长期留存。
关键设计原则包括:
- 审计事件 ID 全局唯一,保证幂等写入。
- trace id / span id / request id 与业务日志打通。
- 记录操作者、主体类型、资源类型、资源 ID、操作类型、结果、风险等级。
- 操作前后快照只存摘要或脱敏后的必要字段,避免审计表二次泄露。
- 审计日志默认追加写,不允许业务侧更新或删除。
- 支持按时间分区、定期归档、留存策略和合规检索。
字段加密、查询与脱敏边界
字段安全要分层处理。
| 层面 | 目标 | 注意点 |
|---|---|---|
| 落库加密 | 防止数据库泄露后明文暴露 | 关注密文长度、key id、轮换、历史兼容 |
| 查询支持 | 支持必要检索 | 等值查询可用哈希辅助列,模糊查询需专门方案 |
| 接口脱敏 | 控制输出给前端或外部系统的数据 | 按角色、场景、字段等级脱敏 |
| 日志脱敏 | 防止日志、异常、审计、消息体泄露 | 覆盖 request、response、stacktrace、fallback 文件 |
| 导出脱敏 | 控制批量数据离线传播 | 审批、留痕、权限、文件加密 |
不能让业务方误以为“加一个注解”就同时解决存储、查询、日志、导出和缓存中的全部敏感数据问题。
分区、归档与迁移治理
分区管理组件只应服务框架自身或横切治理表,例如 audit_log、sql_audit_log、job_log、notification_log,不应擅自管理业务核心表分区。
分区表治理要明确:
- 分区键必须与主要查询条件一致。
- 自动建未来分区,缺失分区要告警。
- 历史分区支持归档、压缩、只读。
- 删除过期分区必须走审批或配置白名单。
- DDL 执行要避开高峰并记录审计。
公共组件升级如果涉及建表、改字段、加索引,必须提供 migration 文件、回滚说明和兼容窗口。禁止组件启动时静默执行高风险 DDL。大表变更建议采用 expand-migrate-contract:先扩展字段,再迁移数据,最后收缩旧结构。
八、配置、失败模式与错误处理矩阵
公共组件必须明确配置边界。每个 Starter 都应该有自己的 Properties,例如 TraceProperties、AuditProperties、EncryptionProperties、RateLimitProperties、MonitorProperties、NotificationProperties。
配置模型要遵守五条原则:
- prefix 稳定,例如
grace.audit、grace.encrypt。 - 默认值安全。
- 生产危险配置启动时阻断。
- 配置项命名表达业务含义,而不是实现细节。
- 热更新配置和需重启配置明确区分。
公共组件还必须明确失败模式,不同能力失败时不能一刀切。
| 阶段 | 示例 | 默认策略 | 是否阻断 | 是否告警 |
|---|---|---|---|---|
| 启动期 | 生产环境 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 周期和回滚方案? |
十、平台化治理与度量体系
公共组件建设是组织级生产力投资,不是技术团队自嗨。它要回答三个问题:
- 是否降低了新服务交付成本?
- 是否减少了跨团队重复建设?
- 是否提升了安全、合规、稳定性和可观测性底线?
成熟度分级
公共组件不应该一上线就强制全团队接入。建议分为四级:
| 阶段 | 含义 | 推广策略 |
|---|---|---|
| Sandbox | 实验能力,仅验证设计 | 不建议业务依赖 |
| Pilot | 试点能力,限定 1-2 个业务接入 | 收集接入成本和问题 |
| Stable | 稳定能力,可推荐接入 | 提供文档、示例、支持 SLA |
| Standard | 标准能力,新服务默认接入 | 纳入脚手架、质量门禁和架构规范 |
Trace、Audit、Encryption、Desensitization、RateLimit 这类基础能力,成熟后应逐步从“推荐接入”升级为“新服务默认接入”。
owner 与评审机制
每个公共组件都必须有 owner。owner 负责:
- 路线图。
- API 评审。
- 版本发布。
- 线上问题响应。
- 兼容性承诺。
- 接入文档。
- 业务团队反馈闭环。
重大能力进入公共层前,建议通过 RFC 或 ADR 评审,评审内容包括:复用范围、抽象边界、依赖风险、数据风险、默认行为、失败模式、测试覆盖、文档完备度和维护 owner。
准入、准出和迁移
公共组件不仅要有准入标准,也要有准出机制。
当一个组件长期无人维护、使用量极低、能力被替代,或设计已经无法满足新架构时,应进入 deprecated 流程:
- 标记 deprecated。
- 发布替代方案。
- 提供迁移指南。
- 给出下线时间表。
- 跟踪业务服务迁移进度。
- 到期后移除或冻结维护。
没有准出机制,公共组件仓库会不断膨胀,最后变成没人敢删、没人敢动的历史包。
度量指标
没有度量,公共组件就很难证明平台化价值。
| 指标 | 说明 |
|---|---|
| 接入覆盖率 | 多少服务接入 Trace、Audit、Encryption、RateLimit |
| 接入成本 | 新服务接入耗时、配置项数量、业务代码改动行数 |
| 质量收益 | 相关线上故障数、重复缺陷减少量 |
| 安全合规收益 | 敏感数据加密覆盖率、审计日志完整率 |
| 维护效率 | 版本升级成功率、破坏性变更次数、平均问题响应时间 |
| 运行健康度 | fallback rate、Kafka lag、慢 SQL 数、连接池耗尽次数 |
| 推广效果 | Pilot 到 Stable 的转化率、业务团队反馈响应时间 |
公共组件要像平台产品一样运营,周期性输出健康度报告,而不是只在仓库里维护代码。
最后总结三个关键收获:
- 公共组件不是 common 包,而是把跨服务标准能力产品化,把安全、合规、可观测和稳定性变成默认路径。
- 好的公共组件必须同时具备技术边界和治理边界:依赖可控、Starter 可替换、失败可预期、数据风险可审计、版本可演进。
- 公共架构框架的目标不是封装一切,而是降低系统整体复杂度;如果缺少 owner、测试、度量和迁移机制,公共组件本身也会变成新的技术债。

浙公网安备 33010602011771号