开发与重构计划
DDK 的构建方式、模块划分、测试要求,以及当前的重构排期与已知缺陷清单。
一、环境与构建
| 项 | 要求 |
|---|---|
| JDK | 21 |
| Maven | 3.9+ |
| Spring Boot | 4.1.1(根 pom 的 parent,Jackson 3) |
| CI | Java 21 与 Java 25 两个版本 |
# 全量构建 + 测试(CI 用的就是这条)
mvn -B -ntp verify
# 安装到本地仓库
mvn -B install
# 只构建某个模块及其依赖
mvn -B -pl ddk-starters/ddk-cache-starter -am install不要用 -DskipTests。 它只跳过测试执行、不跳过测试编译。项目历史上正是因为习惯性加这个参数,一个测试类里的 Kotlin 式 import ... as ... 语法错误让整个 reactor 长期构建失败而无人发现——ddk-cache-starter 之后的 8 个模块全部 SKIPPED。真要跳过测试用 -Dmaven.test.skip=true,但那只应该用于临时排查。
CI 配置在 .github/workflows/build.yml,push 和 PR 都会跑 mvn verify。
二、模块划分
domain-driven-kit
├── ddk-dependencies BOM,下游 import 后无需再写版本号
├── ddk-core 核心抽象,不依赖持久层框架
├── ddk-mybatis ddk-core 的 MyBatis-Plus 实现
├── ddk-starters 8 个 Spring Boot starter
│ ├── ddk-web-starter
│ ├── ddk-mybatis-starter
│ ├── ddk-redis-starter
│ ├── ddk-cache-starter
│ ├── ddk-db-starter
│ ├── ddk-tracer-starter
│ ├── ddk-seata-starter
│ └── ddk-archguard-starter
├── ddk-archetypes 三层 / 四层 Maven archetype
└── ddk-examples 可运行示例(ddk-example-user)模块依赖约束:
ddk-core不得依赖任何持久层框架。com.ddk.core.domain包更严格,不得依赖任何框架ddk-mybatis依赖ddk-core+ MyBatis-Plus- starter 依赖
ddk-core,可以依赖ddk-mybatis,不得互相依赖
三、写 starter 的规范
新增或改造 starter 时按这套模式,八个 starter 目前风格不统一,正在逐步收敛:
1. 独立配置前缀 + @ConfigurationProperties
@ConfigurationProperties(prefix = "ddk.xxx")
public class DdkXxxProperties {
private boolean enabled = true;
// ...
}不要复用 Spring Boot 自己的前缀(如 spring.cache.*)。ddk-cache-starter 复用了,结果和 Boot 的自动配置产生循环条件,上下文起不来。
2. 显式声明加载顺序
@AutoConfiguration(before = SomeBootAutoConfiguration.class, after = AnotherAutoConfiguration.class)不要靠 @Primary 硬压 Boot 的自动配置——那只是掩盖顺序问题。
3. 依赖不可用时降级,不要启动失败
starter 提供的是增强能力,不该成为可用性单点。Redis 连不上就退化成本地缓存,追踪组件缺失就不注册,都比启动失败好。
4. 可选依赖用 ObjectProvider
// 错:@Bean 方法参数上的 @Autowired(required=false) 不生效,缺 Bean 会抛异常
public CacheManager cacheManager(@Autowired(required = false) CacheManager delegate) { }
// 对
public CacheManager cacheManager(ObjectProvider<CacheManager> provider) {
CacheManager delegate = provider.getIfAvailable();
}5. 注册 AutoConfiguration.imports
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports不要用 Spring Boot 2 的 spring.factories。
6. 提供配置元数据
src/main/resources/META-INF/additional-spring-configuration-metadata.json目前没有任何 starter 提供,IDE 里没有配置补全。根 pom 的 annotationProcessorPaths 已经加上 spring-boot-configuration-processor,缺的是元数据文件本身。
7. 库里不要携带全局配置文件
库 jar 里的 logback-spring.xml、application.yml 会随 classpath 生效,覆盖使用方的配置。ddk-web-starter 早期就携带过 logback-spring.xml,已经移除;日志、端口这类全局配置只能由应用自己决定。
四、测试要求
| 模块类型 | 要求 | 现状 |
|---|---|---|
ddk-core | 纯单元测试,不启动容器 | 有 |
ddk-mybatis | 单测 + H2 或 Testcontainers 集成测试 | 单测 + H2 集成测试 |
| starter | ApplicationContextRunner 测试装配条件 + 降级行为;依赖外部组件的用 Testcontainers | 全部有;cache 用 Testcontainers 启动 Redis |
| archetype | 集成测试生成项目并执行 mvn verify | 两个 archetype 都有,CI 通过 -Parchetype-it 开启 |
| example | 通过 HTTP 走完整用例 + 架构测试 | ddk-example-user 有 |
com.ddk.core.domain 额外有一条:DomainPackagePurityTest 断言整个包只依赖 JDK 与自身。加任何第三方依赖都会让它失败——这是有意的,领域基类是要被下游继承的。
断言行为,不要断言 Bean 类型。
这条是从真实教训里来的。ddk-cache-starter 的测试断言 assertThat(manager).isInstanceOf(CompositeCacheManager.class),断言通过了,但它保护的是实现细节,完全没发现「Redis 层根本不会被访问」这个根本性缺陷。正确的断言是「往 Redis 直接写一个值,读取时能命中并回填到 L1」。
ApplicationContextRunner 有两个坑要知道:
- 它不读取 classpath 上的
AutoConfiguration.imports,只处理显式传入AutoConfigurations.of(...)的类。要验证第三方 Bean,必须把对应的自动配置一并列出 - 它不执行
spring.factories里注册的ApplicationContextInitializer。依赖这些初始化器的框架(如 Seata)无法在这种测试里验证,必须用@SpringBootTest
五、当前缺陷清单
按严重程度排列。P0 会导致功能不可用或运行时崩溃。
P0
| 缺陷 | 位置 | 状态 |
|---|---|---|
ddk-cache-starter 用 CompositeCacheManager 实现多级缓存,Redis 层永不被访问 | ddk-cache-starter | ✅ 已按设计文档重写为 TwoLevelCache |
archetype 不是可用的 Maven archetype(缺 archetype-metadata.xml 等) | ddk-archetypes | ✅ 已改造,含生成项目的集成测试 |
ddk-examples 完全是空的 | ddk-examples | ✅ 已补 ddk-example-user |
DefaultMapper 把数据映射成空 Object,仓储调用运行时抛 ClassCastException | ddk-core mapper + ddk-mybatis | ✅ 已修,改为抛 MissingMapperException |
MapperProvider 用 @Component,库 jar 里靠使用方 component scan 才能注册 | ddk-core | ✅ 已修,改由 ddk-mybatis-starter 自动配置注册 |
BaseExceptionHandler 缺 @RestControllerAdvice,全局异常处理不生效 | ddk-web-starter | ✅ 已修 |
| 整个 reactor 构建失败 | ddk-cache-starter 测试语法错误 | ✅ 已修 |
MultiDataSourceAutoConfiguration 作为 BFPP 却有构造器注入,上下文起不来 | ddk-db-starter | ✅ 已修 |
| 无 CI | 仓库 | ✅ 已加 |
Redis 序列化用 LaissezFaireSubTypeValidator 开启默认类型,写得进 Redis 就能让应用实例化任意类 | ddk-redis-starter | ✅ 已修,改为包白名单 |
P1
| 缺陷 | 位置 | 状态 |
|---|---|---|
CORS 默认 allowedOriginPatterns("*") + allowCredentials(true) 全放开且不可配 | ddk-web-starter | ✅ 已修,默认关闭,ddk.web.cors.* 可配 |
库里携带 logback-spring.xml,会覆盖使用方的日志配置 | ddk-web-starter | ✅ 已删 |
web / redis 两个 starter 没有测试 | ddk-starters | ✅ 已补 |
AggregateRoot.version() 没有在 GenericRepositoryImpl 里打通乐观锁 | ddk-mybatis | ✅ 已打通,默认装 OptimisticLockerInnerInterceptor |
DomainEventPublisher 的 Spring 实现还在 archetype 里,没有收进 starter | ddk-starters | ✅ 已收进 ddk-event-starter |
三层 archetype 只有一个 Application 类 | ddk-archetypes | ✅ 已补齐,含三层架构规则 |
ddk-core 无领域模型基类 | ddk-core | ✅ 已实现,文档 |
MapperProvider 类型键用 SimpleName,跨包同名会静默覆盖 | ddk-core | ✅ 已修,改用全限定名 |
MapperProvider 在构造器里触发全量 Bean 实例化 | ddk-core | ✅ 已修,改为 SmartInitializingSingleton |
ddk-dependencies 是空壳 BOM | ddk-dependencies | ✅ 已填充 |
ddk-core / ddk-mybatis 完全没有测试 | 两个核心模块 | ✅ 已补,79 个 |
mybatis-starter 没有测试 | ddk-starters | ✅ 已补 |
已修记录
| 问题 | 位置 | 状态 |
|---|---|---|
PageQuery.addSort() 无效,排序被静默忽略 | ddk-mybatis | ✅ 已修 |
PageQuery.pageSize 无上限 | ddk-core | ✅ 已修 |
CommonArchRules 允许 Domain→Infrastructure,违反 DIP | ddk-archguard-starter | ✅ 已修 |
GenericRepository 泛型顺序 <ID, E> 与 javadoc 及社区习惯相反 | ddk-core | ✅ 已改为 <E, ID> |
P2
| 缺陷 | 位置 |
|---|---|
| 无 checkstyle / spotless;jacoco 只出报告没有门槛 | 根 pom |
| pom description 与实现不符(redis 宣称分布式锁/限流、mybatis 宣称数据权限,均未实现) | 两个 starter |
自动配置类与框架同名(MybatisPlusAutoConfiguration),排错易混淆 | ddk-mybatis-starter |
archunit-junit5-api 是 compile 作用域 | ddk-archguard-starter |
Specification 不能下推到 SQL,复杂查询仍要走 Mapper XML | ddk-core / ddk-mybatis |
六、重构排期
阶段 0 · 恢复构建(已完成) 修测试语法错误、修 db-starter 的 BFPP 问题、修 cache-starter 上下文启动、对齐 Seata 包名、补 tracer 依赖、加 CI。
阶段 1 · 工程基线(已完成) ✅ ddk-dependencies 填充成真 BOM;✅ CI;✅ 根 pom 补 spring-boot-configuration-processor; ✅ JaCoCo 按模块设门槛(行 ≥ 70%、分支 ≥ 50%);✅ Spotless 检查未使用 import 与空白字符(不做整体格式化,避免与 150 列单行风格冲突)。
阶段 2 · 领域模型与仓储(已完成) ✅ Identifier / ValueObject / Entity / AggregateRoot / DomainEvent / Specification; ✅ MapperProvider 找不到映射器直接失败、key 用全限定名、由自动配置注册; ✅ 通用仓储打通乐观锁与领域事件发布;✅ ddk-core 单测与 ddk-mybatis 的 H2 集成测试。
阶段 3 · starter 规范化(已完成) ✅ 全部 starter 统一 ddk.* 前缀并生成配置元数据;✅ 重写 ddk-cache-starter 为两级缓存;✅ Redis 反序列化白名单; ✅ CORS 默认关闭且可配、移除 logback-spring.xml;✅ CommonArchRules 用测试夹具验证能抓到违规。
阶段 4 · archetype 与示例(已完成) ✅ 三层 / 四层骨架改造成 Maven archetype,集成测试构建生成的项目;✅ ddk-examples/ddk-example-user 可运行示例。
阶段 5 · 发布就绪 稳定包名与公开契约、语义化版本策略、公开 API 的 Javadoc。
暂不发布到 Maven 中央仓库,因此 gpg 签名、distributionManagement、javadoc 插件等发布相关配置不在排期内。
七、提交约定
- 提交信息用
<type>: <描述>,type 取feat/fix/refactor/docs/test/chore - 一次提交只做一件事。修构建和加特性不要混在一起
- 改动 starter 行为时,同步更新 codesphere 文档站对应的 starter 文档
- 不要提交
target/或 IDE 配置