大家好,我是春豆李。
在长期的 Java 后端研发过程中,最让人头疼的两件事往往是:“接口文档与代码脱节” 以及 “不知道半年前的前辈为什么写出这种诡异的设计”。
在传统的敏捷迭代中,文档常常沦为 Wiki 里的“二等公民”。随着 Spring Boot 3.x 的普及以及 GraalVM Native Image 在云原生场景下的逐步落地,我们的构建链路越来越快,文档如果跟不上 CI/CD 的节奏,腐化速度会呈指数级上升。
今天和大家聊聊我们在 Java 后端落地 “文档即代码(Docs as Code)” 与 ADR(Architectural Decision Record,架构决策记录) 的具体工程化方案,包含踩坑总结与工具链配置。
一、 API 文档工程化:Springdoc OpenAPI 3.x 与静态化导出
在 Spring Boot 2.x 时代,不少团队使用的是 springfox-boot-starter,但该项目早已停止维护。升级到 Spring Boot 3.x(基于 Spring Framework 6)后,官方推荐的标准方案是迁移至 springdoc-openapi(当前推荐 2.5.x 以上版本)。
1. 依赖与基础配置
在 pom.xml 中引入核心依赖:
<properties>
<spring-boot.version>3.2.5</spring-boot.version>
<springdoc.version>2.5.0</springdoc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
</dependencies>
2. 代码级契约定义
尽量避免让 Controller 充斥过多的文档注解,推荐通过 @Operation 和 @Schema 完成核心语义约束:
@RestController
@RequestMapping("/api/v1/orders")
@Tag(name = "Order API", description = "订单管理核心接口")
public class OrderController {
@PostMapping
@Operation(summary = "创建订单", description = "根据购物车项与用户上下文生成待支付订单")
public ResponseEntity<OrderResponse> createOrder(
@Valid @RequestBody CreateOrderRequest request) {
// 业务逻辑略
return ResponseEntity.ok(new OrderResponse("ORD-20240501-001", OrderStatus.PENDING_PAY));
}
}
@Schema(description = "订单创建请求体")
public record CreateOrderRequest(
@Schema(description = "用户唯一 ID", example = "U10086")
@NotBlank String userId,
@Schema(description = "商品 SKU 列表")
@NotEmpty List<String> skuIds
) {}
3. 踩坑点:云原生 AOT 与 Native Image 兼容
如果你的应用正尝试使用 GraalVM 编译为 Native Image(原生镜像),请务必注意:Springdoc 在 Native 镜像运行时可能会因为深度反射导致上下文解析异常。
最佳实践:
- 在微服务集群中,不建议直接把 Swagger UI 打包暴露在生产环境的 Pod 中。
- 利用
springdoc-openapi-maven-plugin,在 Maven 构建阶段启动集成测试容器,自动导出静态的openapi.json/openapi.yaml。 - CI 流程拉取导出的 YAML 文件,统一推送到团队的 API Gateway 或内部 Redoc / YApi 平台,实现构建期文档生成,彻底规避 Native 反射开销。
二、 ADR 架构决策代码化:记录技术选型的“为什么”
代码能告诉你系统“是怎么实现的(How)”,Git Commit 能告诉你“改了什么(What)”,但架构背景与取舍原因(Why),往往只存在于当时的会议纪要或设计者的脑海中。
ADR(Architectural Decision Records)的核心思想,是将每次重大架构或技术选型以轻量级 Markdown 的形式保存在代码仓库的 docs/adr/ 目录下,与业务代码一同进行 Code Review 并随 Git 版本流转。
1. ADR 目录结构规范
my-backend-service/
├── docs/
│ └── adr/
│ ├── 0001-record-architecture-decisions.md
│ ├── 0002-migrate-to-spring-boot-3-and-jdk-21.md
│ └── 0003-use-caffeine-for-l1-cache.md
├── src/
└── pom.xml
2. 标准 ADR 模板示例(基于 MADR 规范)
# 0003. 使用 Caffeine 作为一级本地缓存
* 状态: accepted
* 决策人: @春豆李, @后端组
* 决策日期: 2024-05-10
## 背景与问题描述
当前商品详情接口在高并发压测下,Redis 连接池打满,QPS 受限在 8,000 左右。
需要引入本地内存缓存(L1)配合 Redis(L2)构建多级缓存,减轻远程缓存压力。
## 备选方案
1. Guava Cache
2. Caffeine Cache
3. 自研 ConcurrentHashMap + 定时清理
## 决策依据与理由
选择 **Caffeine**。
- **性能更优**:采用 Window TinyLFU 淘汰策略,吞吐量和命中率在多数基准测试中优于 Guava。
- **Spring 官方支持**:Spring Boot 3.x 的 `CacheManager` 提供了对 Caffeine 的一等公民支持。
- **可维护性**:开箱即用支持最大容量、基于时间过期与统计指标暴露(可接入 Micrometer/Prometheus)。
## 潜在风险与补偿策略
- **内存泄漏风险**:需严格配置 `maximumSize`,Native 镜像环境下需配置 JNI 限制。
- **数据一致性**:L1 缓存无法做到强一致,需通过 Redis Pub/Sub 发送 Invalid 广播,失效时间硬性上限设为 5 分钟。
三、 构建即文档:自动化 Pipeline 集成
文档工程化的核心是把文档当成单元测试来跑。如果不加以约束,Markdown 文件很容易出现死链、格式错乱。
1. 文档质量门禁(Pre-commit & CI)
我们在持续集成(CI)流中加入两道门禁:
- Vale / markdownlint-cli:对 ADR 和架构设计文档做语法与排版检查(包括中英文排版留白、标题层级)。
- ADR-Tools:在终端通过统一 CLI 管理编号,防止多人协同发 PR 时出现 ADR 编号冲突:
bash
# 新建决策 adr new "Upgrade to Spring Boot 3.2"
2. Asciidoctor 与 PlantUML 的代码化架构图
架构图不应只存为 PNG 图片。推荐在 src/docs/asciidoc 下使用 AsciiDoc + PlantUML 维护,利用 asciidoctor-maven-plugin 在 mvn package 时自动渲染成 HTML / PDF 发布物:
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>3.0.0</version>
<dependencies>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj-diagram</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>
<configuration>
<requires>
<require>asciidoctorj-diagram</require>
</requires>
</configuration>
</plugin>
四、 实践总结与避坑指南
- 版本演进严格打标:
ADR 状态必须受控,包含
draft(草案)、accepted(已采纳)、superseded(已被新决策取代)。当有新的架构变更覆盖了旧方案时,不要直接修改老 ADR,而是新建一份 ADR 并标记supersedes 0003,保留架构演化脉络。 - 严防契约“伪自动化”:
很多团队虽然用了 OpenAPI,但在 DTO 字段变更后不跑集成测试,导致生成的文档依然是旧的。建议在单元测试中利用
MockMvc+springdoc-openapi-tests进行契约断言,使文档构建失败直接阻断 CI。 - GraalVM 兼容性底线: 在做任何架构决策或引入新的文档生成插件时,注意检查其是否依赖大量动态字节码生成工具(如 ByteBuddy、CGLIB)。在向原生镜像演进的路径上,优先选择在构建期(Compile/AOT Phase)完成工作的轻量级工具。
文档不是负担,而是沉淀工程资产、降低沟通成本的利器。大家团队目前在管理架构选型历史时主要用什么手段?欢迎在评论区交流。
License: CC BY-NC 4.0
Updated an hour ago
Was this article helpful? Give it a like.
0 comments


