V10 编译器生成什么
wow-compiler 是一个 KSP 处理器。在 generate 模式下(Gradle 插件会设置它),它读取领域模块中带注解的类,把 Kotlin 源码与资源写到 build/generated/ksp/main/。运行时不靠扫描或反射发现任何东西:宿主加载编译器写出的内容。编译器无法绑定的模型会让编译失败,错误信息指出函数与违反的规则。
对 eShop 的 eshop-ordering-domain 模块,编译器写出:
build/generated/ksp/main/
├── kotlin/me/ahoo/wow/example/eshop/ordering/domain/order/
│ ├── EshopOrderingDomainModelProvider.kt 绑定索引
│ ├── OrderModel.kt fun orderModel(context, paymentPolicy, configure)
│ ├── OrderProcessSagaSubscriptions.kt fun orderProcessSagaHandlers(dependencies)
│ └── OrderStateProperties.kt 状态的查询字段(有 wow-query-api 时)
└── resources/META-INF/
├── services/me.ahoo.wow.kernel.ModelProvider
└── wow/manifest/eshop-ordering-domain.json Manifest 片段模型工厂
每个聚合根在它的包里得到一个顶层工厂函数,以根命名:<root>Model,首字母小写(Order 得到 orderModel)。
public fun orderModel(
context: String,
paymentPolicy: PaymentPolicy,
configure: AggregateModelBuilder<OrderState>.() -> Unit = {},
): AggregateModel<OrderState>context总是第一个参数,即使模块只声明了一个@BoundedContext:绑定索引传入它编译时的上下文。测试自己传入:orderModel("eshop-ordering", paymentPolicy)。- 服务随后,命令函数或 after 钩子需要的每个服务一个参数。宿主在构建模型时绑定一次。
configure在生成的绑定之后作用于模型构建器:通用扩展口,很少需要。
工厂通过内核的 DSL(aggregate<S>(context, name) { … })构建模型:每条命令一个 decide(或 decideSuspending)并带创建规则,每个事件一个 evolve(或 evolveTo)并带 revision,每个 @Upgrade 函数一个 upgrade,以及旧名与生命周期命令。
处理器绑定
每个 @Saga、@Projection 或 @Reactor 类在它的包里得到 <processor>Handlers(dependencies)(OrderProcessSaga 得到 orderProcessSagaHandlers),文件名为 <Processor>Subscriptions.kt。它构造处理器一次,构造函数的参数按类型(与限定符)从 dependencies 取得,并为每个处理函数返回一个内核 ProcessorBinding,名为 <processor>.<function>,Saga 的 @OnRejected 处理函数绑定在其上。生成的代码只依赖 wow-kernel:运行时把这些绑定变成订阅。
查询字段:<State>Properties
模块的类路径上有 wow-query-api 时,每个聚合的状态还会得到 <State>Properties:快照查询能命名的 QueryField 组成的对象,包括系统字段与每条 state.* 路径,如 state.items.productId 对应 OrderStateProperties.STATE__ITEMS__PRODUCT_ID。查询 DSL 接受它们;见查询语言。
绑定索引:ModelProvider
每个模块得到一个实现 me.ahoo.wow.kernel.ModelProvider 的类,登记在 META-INF/services/me.ahoo.wow.kernel.ModelProvider 中供 ServiceLoader 使用:
public class EshopOrderingDomainModelProvider : me.ahoo.wow.kernel.ModelProvider {
override val context: String = "eshop-ordering"
override val fragment: String = "META-INF/wow/manifest/eshop-ordering-domain.json"
override fun models(dependencies: Dependencies): List<AggregateModel<*>> = listOf(
orderModel(context, dependencies.get(PaymentPolicy::class.javaObjectType)),
)
override fun processors(dependencies: Dependencies): List<ProcessorBinding<*>> =
listOf(orderProcessSagaHandlers(dependencies)).flatten()
}- 类名来自模块(KSP 选项
wow.manifest.module,使用插件时是 Gradle 项目名):eshop-ordering-domain得到EshopOrderingDomainModelProvider。不以字母开头的名字加前缀Module。 - 包是模块所有模型与处理器共有的最深的包。
- 依赖按类型查找,带
@Name的参数再按限定符查找。在宿主上,Dependencies是应用的 Bean 工厂:带限定符的依赖是该名字的 Bean;不带的是该类型唯一的 Bean,或多个中的 primary Bean。缺少 Bean 时启动失败,并指出类型(MissingDependencyException)。
宿主加载类路径上的每个 ModelProvider(ModelProvider.load()),并提供它们构建的每个模型:默认由服务的类路径决定它提供哪些上下文。wow.contexts.include 与 wow.contexts.exclude 可以只选其中一部分(见配置项)。
Manifest 片段
编译器把模块在服务契约中的部分写到 META-INF/wow/manifest/<module>.json。它写出上下文,并为每个聚合写出:
| 字段 | 内容 |
|---|---|
name、description | 聚合名与它的 @Description |
state | 状态 Schema 的引用,补全后还有它的指纹 |
snapshot | 聚合是否保存快照 |
commands | 每条命令的 name、aliases、schema、creation(REQUIRED、ALLOWED、NONE)、produces 与 routes |
events | 每个事件的 name、aliases、revision 与 schema |
lifecycle | 它处理的生命周期命令(wow.lifecycle.DeleteAggregate 等) |
queries | 它的查询模型(snapshot、events)及路由 |
schedules | 它的命令函数可能定时的命令 |
在聚合之外,上下文还列出它的 subscriptions(每个处理函数及其 kind、source、derives、rejected、schedules 与 start)、它发布的流 published(aggregate、topic、includeState)与它的 errors(每个 @ErrorCode 及其 status 与 description)。
编译器把 Schema 写成引用(#/$defs/<JVM 类名>)。插件的 generateWowManifestFragment 补全它们:按 Jackson 的规则内省每个类型,把 JSON Schema(2020-12)写入 $defs,附带查询关键字(x-wow-*)与状态指纹。补全后的片段在模块资源中取代编译器写出的片段。快照查询从中读取状态 Schema,所以只有片段补全之后,状态字段才能用于筛选与排序。
服务的 Manifest
宿主启动时把类路径上的片段合并成服务的 Manifest,在 GET /wow/manifest 提供,框架的错误码位于顶层。插件的 mergeWowManifest 在构建期把同样的片段合并为 build/wow/manifest.json,以 wow.serviceName 命名,供其他服务作为对端(wowPeers)以及客户端生成器读取。
路由
宿主只依据 Manifest 构建路由;路由由编译器决定。
| 路由 | 来源 |
|---|---|
POST /<context>/<aggregate>/{id}/<command> | 每条命令;<command> 是命令名的 kebab case(ChangePrice 为 change-price) |
POST /<context>/<aggregate>/<command> | 创建类命令(@CreateAggregate、@AllowCreate)另有这一条;ID 由服务端生成 |
DELETE /<context>/<aggregate>/{id} | DeleteAggregate |
POST /<context>/<aggregate>/{id}/wow.lifecycle.<command> | 其他生命周期命令(wow.lifecycle.restore-aggregate、transfer-owner、transfer-space、apply-tags) |
GET /<context>/<aggregate>/{id}/state | 聚合的状态 |
POST、GET /<context>/<aggregate>/snapshot/query | 快照查询模型及其能力描述 |
POST、GET /<context>/<aggregate>/events/query | 事件查询模型及其能力描述 |
每条路由接收什么、回答什么,见 HTTP API。