docs(cqrs): 补充生成式 stream invoker 文档语义

- 更新 CQRS runtime 与生成器文档,补充 generated stream invoker provider / descriptor 的并列表述。

- 说明 runtime 优先消费 generated request / stream invoker 元数据,未命中时回退到既有反射 binding。

- 调整 request-only 措辞,使 reader-facing 文案与现有 generated request invoker 语义保持一致。
This commit is contained in:
gewuyou 2026-04-30 13:14:11 +08:00
parent 8d6fc74b3d
commit 98477068d6
3 changed files with 38 additions and 13 deletions

View File

@ -20,7 +20,7 @@
- `GeWuYou.GFramework.Cqrs`
- 默认 runtime 与业务侧常用基类。
- `GeWuYou.GFramework.Cqrs.SourceGenerators`
- 可选。为消费端程序集生成 `ICqrsHandlerRegistry`运行时优先走生成注册表;只有缺失、不适用,或 fallback 仍需补齐剩余 handler 时,才继续进入反射路径。
- 可选。为消费端程序集生成 `ICqrsHandlerRegistry`并在可用时补充 generated request / stream invoker provider 元数据;运行时会优先消费这些编译期元数据,只有缺失、不适用,或 fallback 仍需补齐剩余 handler 时,才继续进入反射路径。
- `GFramework.Core`
- 架构上下文中实际调用 `ICqrsRuntime`,并在模块初始化时注册 CQRS 基础设施。
@ -120,13 +120,15 @@ var playerId = await this.SendAsync(new CreatePlayerCommand(new CreatePlayerInpu
## 运行时行为
- 请求分发
- `CqrsDispatcher` 按请求实际类型解析 `IRequestHandler<,>`,未找到处理器会抛出异常。
- `CqrsDispatcher` 按请求实际类型解析 `IRequestHandler<,>`,若当前程序集提供 generated request invoker provider则会先复用对应 descriptor 中的处理器服务类型与 invoker 元数据;未命中时仍回退到既有反射 request binding 创建路径。
- 未找到处理器会抛出异常。
- 通知分发
- 通知会分发给所有已注册 `INotificationHandler<>`;零处理器时默认静默完成。
- 默认通知发布器会按容器解析顺序逐个执行处理器,并在首个处理器抛出异常时立即停止后续分发。
- 若容器在 runtime 创建前已显式注册 `INotificationPublisher`,默认 runtime 会复用该策略;未注册时回退到内置顺序发布器。
- 流式请求
- 通过 `IStreamRequest<TResponse>``IStreamRequestHandler<,>` 返回 `IAsyncEnumerable<TResponse>`
- 当消费端程序集提供 generated stream invoker provider / descriptor 后runtime 会优先消费这组 stream invoker 元数据;未命中时仍回退到既有反射 stream binding 创建路径。
- 上下文注入
- 处理器基类继承 `CqrsContextAwareHandlerBase`runtime 会在分发前注入当前 `IArchitectureContext`
- 如果处理器或行为需要上下文注入,而当前 `ICqrsContext` 不是 `IArchitectureContext`,默认实现会抛出异常。
@ -140,6 +142,7 @@ var playerId = await this.SendAsync(new CreatePlayerCommand(new CreatePlayerInpu
- 同一程序集按稳定键去重,避免重复注册。
- 优先尝试消费端程序集上的 `ICqrsHandlerRegistry` 生成注册器。
- 当生成注册器同时暴露 generated request invoker provider 或 generated stream invoker provider 时registrar 会把对应 descriptor 元数据接线到 runtime 缓存。
- 生成注册器不可用或元数据损坏时,记录告警并回退到反射扫描。
- 当程序集声明了 `CqrsReflectionFallbackAttribute` 时,运行时会先执行生成注册器,再只补它未覆盖的 handler。
- `CqrsReflectionFallbackAttribute` 现在可以多次声明,并同时承载 `Type[]``string[]` 两类 fallback 清单。

View File

@ -165,14 +165,25 @@ protected override void OnInitialize()
1. 优先读取消费端程序集上的 `CqrsHandlerRegistryAttribute`
2. 存在生成注册器时优先使用 `ICqrsHandlerRegistry`
3. 生成注册器不可用或元数据异常时记录告警并回退到反射路径
4. 当生成注册器携带 `CqrsReflectionFallbackAttribute` 元数据时,运行时会先完成生成注册器注册,再补剩余 handler
5. `CqrsReflectionFallbackAttribute` 可以同时携带 `Type[]``string[]` 两类清单;运行时会优先复用直接 `Type` 条目,只对名称条目做定向 `Assembly.GetType(...)` 查找
6. 只有旧版空 marker 或生成注册器不可用时,才会回到整程序集反射扫描
7. 同一程序集按稳定键去重,避免重复注册
3. 当生成注册器同时暴露 generated request invoker provider 时runtime 会把 request/response 类型对对应的 descriptor 预先接线到 dispatcher 缓存,后续请求分发优先消费这些 generated request invoker 元数据
4. 当生成注册器同时暴露 generated stream invoker provider 时runtime 会以同样方式优先消费 stream request 对应的 generated stream invoker descriptor只有当前类型对未命中时才回退到既有反射 stream binding
5. 生成注册器不可用或元数据异常时记录告警并回退到反射路径
6. 当生成注册器携带 `CqrsReflectionFallbackAttribute` 元数据时,运行时会先完成生成注册器注册,再补剩余 handler
7. `CqrsReflectionFallbackAttribute` 可以同时携带 `Type[]``string[]` 两类清单;运行时会优先复用直接 `Type` 条目,只对名称条目做定向 `Assembly.GetType(...)` 查找
8. 只有旧版空 marker 或生成注册器不可用时,才会回到整程序集反射扫描
9. 同一程序集按稳定键去重,避免重复注册
换句话说,声明 fallback 特性本身不等于“整包反射扫描”。当前推荐理解是生成注册器负责能静态表达的部分fallback 只补它覆盖不到的 handler。
如果你在阅读 dispatcher 行为,可以把这部分理解成两组并列能力:
- request invoker provider / descriptor
- 面向 `SendRequestAsync(...)``SendAsync(...)``SendQueryAsync(...)` 这类单次请求分发
- stream invoker provider / descriptor
- 面向 `CreateStream(...)` 触发的流式请求分发
两者的共同点都是“优先消费 generated invoker 元数据,未命中时保留既有反射绑定作为兜底”,而不是要求业务侧切换到另一套 runtime 入口。
`Cqrs.SourceGenerators` 的专题入口见[CQRS Handler Registry 生成器](../source-generators/cqrs-handler-registry-generator.md)。
## Pipeline Behavior
@ -225,7 +236,7 @@ RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
| `GFramework.Cqrs.Abstractions/Cqrs/` | `ICqrsRuntime``ICqrsHandlerRegistrar``IPipelineBehavior<,>``IRequestHandler<,>``Unit` | 请求、处理器和 runtime seam 的最小契约 |
| `GFramework.Cqrs/Command` `Query` `Notification` `Request` `Extensions` | `CommandBase<TInput, TResponse>``QueryBase<TInput, TResponse>``NotificationBase<TInput>``RequestBase<TInput, TResponse>``ContextAwareCqrsExtensions` | 业务侧常用基类和上下文发送入口 |
| `GFramework.Cqrs/Cqrs/` | `AbstractCommandHandler<,>``AbstractQueryHandler<,>``AbstractRequestHandler<,>``AbstractStreamCommandHandler<,>``AbstractStreamQueryHandler<,>``LoggingBehavior<,>` | 默认处理器基类、上下文注入、流式处理与行为管道 |
| `GFramework.Cqrs` 根入口与 `Internal/` | `CqrsRuntimeFactory``ICqrsHandlerRegistry``CqrsHandlerRegistryAttribute``CqrsReflectionFallbackAttribute``DefaultCqrsRegistrationService` | runtime 创建入口、generated-registry 优先级、targeted fallback 语义和程序集去重规则 |
| `GFramework.Cqrs` 根入口与 `Internal/` | `CqrsRuntimeFactory``ICqrsHandlerRegistry``CqrsHandlerRegistryAttribute``CqrsReflectionFallbackAttribute``ICqrsRequestInvokerProvider` | runtime 创建入口、generated-registry 优先级、request / stream invoker provider 协作点、targeted fallback 语义和程序集去重规则 |
| `GFramework.Cqrs.SourceGenerators/Cqrs/` | `CqrsHandlerRegistryGenerator``RuntimeTypeReferenceSpec``OrderedRegistrationKind` | 生成注册器、可直接引用类型判定、mixed fallback 发射与诊断边界 |
## 继续阅读

View File

@ -6,7 +6,8 @@ description: 为消费端程序集生成 CQRS handler registry并在需要时
# CQRS Handler Registry 生成器
`GFramework.Cqrs.SourceGenerators` 会在编译期为当前业务程序集生成 `ICqrsHandlerRegistry`,让 `GFramework.Cqrs`
runtime 在注册 handlers 时优先走静态注册表,而不是先扫描整个程序集。
runtime 在注册 handlers 时优先走静态注册表;当运行时合同允许时,也会把 request / stream 分发可直接复用的 invoker
元数据前移到编译期,而不是总是先扫描整个程序集或在首次分发时再走反射绑定。
它服务的是 `Cqrs` 家族,不是独立运行时:
@ -27,11 +28,17 @@ runtime 在注册 handlers 时优先走静态注册表,而不是先扫描整
1. 一个实现 `ICqrsHandlerRegistry` 的内部注册器类型
2. 程序集级 `CqrsHandlerRegistryAttribute`
当运行时暴露对应合同、且当前 handler 可被安全静态表达时,生成注册器还可以继续暴露:
- generated request invoker provider / descriptor
- generated stream invoker provider / descriptor
当某些 handler 不能被生成代码安全地直接引用时,还会补发:
- 程序集级 `CqrsReflectionFallbackAttribute`
这意味着运行时会先使用生成注册器完成可静态表达的映射,再只对剩余类型做补扫,而不是退回整程序集盲扫。
这意味着运行时会先使用生成注册器完成可静态表达的映射;对 request 与 stream 分发来说,也会优先消费 generated invoker
descriptor。只有当前类型对没有 generated metadata或 registry / fallback 无法覆盖时,才继续回到既有反射 binding 或补扫路径,而不是退回整程序集盲扫。
如果这些 fallback handlers 本身仍可直接引用,生成器会优先发射 `typeof(...)` 形式的 fallback 元数据;当 runtime 允许同一程序集声明多个 fallback 特性实例时mixed 场景也会拆成 `Type` 元数据和字符串元数据两段,进一步减少 runtime 再做字符串类型名回查的成本。
## 最小接入路径
@ -78,9 +85,11 @@ RegisterCqrsHandlersFromAssemblies(
1. 先读取程序集上的 `CqrsHandlerRegistryAttribute`
2. 优先激活生成的 `ICqrsHandlerRegistry`
3. 若生成元数据损坏、registry 不可激活,记录告警并回退到反射路径
4. 若存在 `CqrsReflectionFallbackAttribute`,只补扫剩余 handler
5. 同一程序集按稳定键去重,避免重复注册
3. 若生成注册器同时提供 request invoker provider / descriptorregistrar 会把这些 request invoker 元数据预先登记到 dispatcher 缓存
4. 若生成注册器同时提供 stream invoker provider / descriptorruntime 也会优先消费对应的 generated stream invoker 元数据;未命中时仍回退到既有反射 stream binding
5. 若生成元数据损坏、registry 不可激活,记录告警并回退到反射路径
6. 若存在 `CqrsReflectionFallbackAttribute`,只补扫剩余 handler
7. 同一程序集按稳定键去重,避免重复注册
这个行为由 `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs`
`GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 共同覆盖。
@ -91,6 +100,7 @@ RegisterCqrsHandlersFromAssemblies(
- 业务程序集内 handler 数量较多
- 想把 handler 注册路径前移到编译期
- 想把 request / stream 分发里可静态确定的 invoker metadata 一并前移到编译期
- 希望冷启动阶段减少整程序集反射扫描
- 需要更明确地观察“哪些 handler 走静态注册,哪些只能走 fallback”
@ -127,6 +137,7 @@ RegisterCqrsHandlersFromAssemblies(
- `GFramework.Cqrs.ICqrsHandlerRegistry`
- `GFramework.Cqrs.CqrsHandlerRegistryAttribute`
- `GFramework.Cqrs.CqrsReflectionFallbackAttribute`
- `GFramework.Cqrs.ICqrsRequestInvokerProvider`
- `GFramework.Cqrs.SourceGenerators.Cqrs.CqrsHandlerRegistryGenerator`
模块族入口见: