接入现有 Spring Boot 项目
先把 Wow 作为一条可回退的垂直切片接入:
KSP 元数据 → Spring 自动装配 → 生成 HTTP 路由
→ 命令聚合 → 事件存储 / 快照 → 版本化状态读取不要在同一次改动中迁移所有写入链路或引入全部生产基础设施。第一个里程碑是:一个聚合在显式内存 Adapter 上运行,原有写入路径仍可用于回退。
版本基线
当前 Wow 9.0.18 源码声明:
| 组件 | 版本 |
|---|---|
| JDK | 17+ |
| Wow | 9.0.18 |
| Spring Boot | 4.1.1 |
| Kotlin | 2.4.20 |
| KSP | 2.3.11 |
| CosId | 3.2.1 |
| Springdoc | 3.1.1 |
这些版本是一条兼容性列车,不是可以独立替换的建议。使用其他 Wow 版本时,先检查对应 tag 的 gradle/libs.versions.toml、发布说明和持久化事件要求,再修改应用。
1. 定义接入边界
选择一个聚合 ID 与业务不变量都清晰的用例。编辑前记录:
- 现有 HTTP/写入入口与当前事实来源;
- 将引入的 Wow 命令路由;
- 首次运行只在本地,还是会接收真实流量;
- 切换前需要的状态对比或对账;
- 最后一个只需移除 Wow 依赖即可回退的节点。
新路由尚无生产流量时,回退很简单。命令已经追加事件后,移除运行时并不等于数据回滚;详见回滚边界。
2. 选择依赖与 Capability
每个包含 Wow 注解的模块都要应用 KSP 并依赖 wow-compiler。多模块服务可按最小职责拆分:
| 模块 | 职责 | 必需 Wow 组件 |
|---|---|---|
api | 限界上下文、命令、事件、@CommandRoute | wow-api、KSP、wow-compiler |
domain | @AggregateRoot、@OnCommand、@OnSourcing、领域规约 | wow-core 或 wow-spring、KSP、wow-compiler、wow-test |
server | Spring Boot 宿主与运行时 Adapter | 基础 Starter 与选定 Capability |
在注解模型模块中对齐平台与编译器:
dependencies {
implementation(platform("me.ahoo.wow:wow-bom:9.0.18"))
ksp(platform("me.ahoo.wow:wow-bom:9.0.18"))
implementation("me.ahoo.wow:wow-api") // api 模块
ksp("me.ahoo.wow:wow-compiler")
testImplementation("me.ahoo.wow:wow-test") // domain 模块
}在服务模块同时请求基础 Starter 与 webflux-support Feature:
dependencies {
implementation(platform("org.springframework.boot:spring-boot-dependencies:4.1.1"))
implementation(platform("me.ahoo.wow:wow-bom:9.0.18"))
implementation("me.ahoo.wow:wow-spring-boot-starter")
implementation("me.ahoo.wow:wow-spring-boot-starter") {
capabilities {
requireCapability("me.ahoo.wow:webflux-support")
}
}
implementation("me.ahoo.cosid:cosid-spring-boot-starter:3.2.1")
}基础 Starter 提供核心 Spring 装配;webflux-support 选择 wow-webflux,由它把 RouterSpecs 物化为命令与状态路由。它是 Gradle Feature Capability,不是配置属性开关。
只选择目标运行环境实际需要的后端:
| 需求 | Capability |
|---|---|
| 本地单进程验证 | 不需要;使用内存配置 |
| 分布式命令/事件/状态总线 | kafka-support |
| MongoDB 事件/快照存储与查询 | mongo-support |
| Redis 事件/快照存储或总线 | redis-support |
| Elasticsearch 存储与查询 | elasticsearch-support |
| 链路追踪仪表器 | opentelemetry-support |
| CoSec 授权 | cosec-support |
完整 Capability 表见 Spring Boot Starter。不要因为“未来可能使用”就提前请求 Kafka 或存储 Feature。
Gradle + KSP 边界
仓库当前验证过的自动元数据生成链路是 Gradle + KSP。Maven 可以声明运行时依赖,但本站不声称存在等价的已验证 Maven 生成链路。依赖生成处理器时,发布前必须证明 META-INF/wow-metadata.json 存在。
3. 生成并检查元数据
按聚合与不变量定义限界上下文、命令/事件、聚合、状态和至少一个 AggregateSpec。业务不变量保留在聚合中,不要复制到 Controller。
在实际注解模型模块中运行 KSP 与测试:
./gradlew clean :api:kspKotlin :domain:kspKotlin :domain:test
test -s api/build/generated/ksp/main/resources/META-INF/wow-metadata.json
test -s domain/build/generated/ksp/main/resources/META-INF/wow-metadata.json按应用调整模块路径。只有配置且没有 Wow 注解的模块,不需要伪造元数据文件。
运行时 MetadataSearcher 会合并 classpath 中所有名为 META-INF/wow-metadata.json 的资源。服务模块必须依赖注解模型模块,确保这些资源进入运行 classpath。不要手写元数据,也不要提交 build/ 产物。
4. 使用显式首跑 Adapter
Wow 的事件存储与快照默认选择 MongoDB;存在外部总线 Adapter 时,也可能激活外部实现。本地单进程验证应显式选择全部内存实现,并关闭依赖 MongoDB 或 Redis 的 PrepareKey:
spring:
application:
name: demo-service
cosid:
machine:
enabled: true
distributor:
type: manual
manual:
machine-id: 1
generator:
enabled: true
wow:
prepare:
enabled: false
command:
bus:
type: in_memory
event:
bus:
type: in_memory
eventsourcing:
store:
storage: in_memory
snapshot:
storage: in_memory
strategy: all
state:
bus:
type: in_memory这不是生产配置:进程退出后数据消失,消息只在单进程内投递,手动 machine ID 1 也只适用于一个实例。请把该本地 Profile 以不含密钥的方式纳入版本控制,并显式激活。
5. 证明运行时路由装配
启动应用实际使用的服务任务,例如:
./gradlew :server:bootRun路由按以下链路完成装配:
- KSP 资源进入运行 classpath;
MetadataSearcher合并限界上下文、聚合、命令和处理函数元数据;- OpenAPI 自动配置创建
RouterSpecs; - WebFlux 自动配置注册命令/状态 Route Module;
RouterFunctionBuilder把路由目录物化为 SpringRouterFunction。
先确认应用日志显示每个注解模块的 META-INF/wow-metadata.json 已加载,再检查 /v3/api-docs 或应用路由目录,确认命令和版本化状态路径存在。如果需要 Swagger UI,可按同一 Springdoc 基线作为应用选择加入;运行时路由本身不依赖 UI。
完成该 UI 配置后,本地约定入口为 http://localhost:8080/swagger-ui.html。
6. 证明命令 → 事件 → 状态
使用正在接入的聚合所生成的路由,不要用手写 Controller 作为测试捷径。
- 使用固定聚合 ID 和唯一请求 ID 发送创建命令。
- 本次接入证明至少请求
SNAPSHOT。 - 同时要求 HTTP 成功、
succeeded: true、errorCode: Ok、预期阶段、ID 与版本。 - 按返回版本读取
/tenant/{tenantId}/{aggregateName}/{id}/state/{version}。 - 对比溯源状态、预期领域结果与
AggregateSpec断言。
具体且已验证的请求/响应见快速上手。请把 Demo 路由与载荷替换为应用生成的契约,不要把 Demo 复制进生产代码。
失败检查点
| 现象 | 首先检查 | 不要做 |
|---|---|---|
| KSP 任务成功但元数据文件不存在 | 包含注解的模块是否应用 KSP 与 wow-compiler | 手写 wow-metadata.json |
| 元数据存在但 HTTP 路由缺失 | 注解模块是否进入服务运行 classpath;是否选择 webflux-support | 添加重复 Controller |
| 启动时请求 Kafka 或 MongoDB | 本地 Profile 是否加载;是否误选后端 Capability;内存配置键是否齐全 | 启动无关基础设施掩盖错误配置 |
| 第一条命令报告 ID 生成器未初始化 | CosId Starter、Generator 与单实例 machine ID 是否生效 | 用临时随机数回退生成生产 ID |
| 命令返回重复请求 | 是否复用了同一 requestId | 关闭幂等以让请求通过 |
状态路由返回 404 | 完整上下文/聚合/租户/ID、命令结果与返回版本 | 假设命令 HTTP 200 已证明状态持久化 |
若路由缺失,继续阅读故障排查:元数据或处理函数未注册。
回滚边界
真实流量进入前,回滚只是移除代码/配置:保持原写入路径不变,关闭新 Wow 路由或 Profile;切片未通过门禁时可移除新增依赖。
Wow 命令已经追加领域事件后,回滚含义发生变化:
- 不要删除事件来模拟数据库回滚;
- 没有明确双写与对账设计时,不要让同一业务写入同时经过新旧路径;
- 对比状态期间,把旧路径保持为只读或与新流量隔离;
- 把流量切回旧路径前,定义已接收 Wow 事件的重放、对账或业务补偿方式;
- 只有在选定持久化 Adapter 上验证备份恢复、幂等、监控与恢复后,才执行切换。
在生产证据完整前,迁移单元应始终保持为一个聚合/用例。
完成门禁
只有以下条件全部成立,接入才具备进入独立切换决策的资格:
- 领域测试通过,每个注解模型模块都存在元数据文件;
- 服务加载这些资源,并暴露生成路由;
- 真实命令到达声明的等待阶段;
- 版本化溯源状态与预期事件历史一致;
- 失败、幂等、存储和回滚流程都有环境证据。
下一步
- 选择持久化 Adapter:Spring Boot Starter
- 定义完成语义:完成语义
- 建立发布门禁:Wow 应用测试
- 建立持久化事件策略:事件演进
- 规划模块边界:模块依赖