From 98477068d68bbc86558324310ee2ace608476a4a Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:14:11 +0800 Subject: [PATCH] =?UTF-8?q?docs(cqrs):=20=E8=A1=A5=E5=85=85=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=BC=8F=20stream=20invoker=20=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 CQRS runtime 与生成器文档,补充 generated stream invoker provider / descriptor 的并列表述。 - 说明 runtime 优先消费 generated request / stream invoker 元数据,未命中时回退到既有反射 binding。 - 调整 request-only 措辞,使 reader-facing 文案与现有 generated request invoker 语义保持一致。 --- GFramework.Cqrs/README.md | 7 ++++-- docs/zh-CN/core/cqrs.md | 23 ++++++++++++++----- .../cqrs-handler-registry-generator.md | 21 +++++++++++++---- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/GFramework.Cqrs/README.md b/GFramework.Cqrs/README.md index 65a92db5..b3176983 100644 --- a/GFramework.Cqrs/README.md +++ b/GFramework.Cqrs/README.md @@ -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` 和 `IStreamRequestHandler<,>` 返回 `IAsyncEnumerable`。 + - 当消费端程序集提供 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 清单。 diff --git a/docs/zh-CN/core/cqrs.md b/docs/zh-CN/core/cqrs.md index 1fd1f601..697656cc 100644 --- a/docs/zh-CN/core/cqrs.md +++ b/docs/zh-CN/core/cqrs.md @@ -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>(); | `GFramework.Cqrs.Abstractions/Cqrs/` | `ICqrsRuntime`、`ICqrsHandlerRegistrar`、`IPipelineBehavior<,>`、`IRequestHandler<,>`、`Unit` | 请求、处理器和 runtime seam 的最小契约 | | `GFramework.Cqrs/Command` `Query` `Notification` `Request` `Extensions` | `CommandBase`、`QueryBase`、`NotificationBase`、`RequestBase`、`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 发射与诊断边界 | ## 继续阅读 diff --git a/docs/zh-CN/source-generators/cqrs-handler-registry-generator.md b/docs/zh-CN/source-generators/cqrs-handler-registry-generator.md index 21436faa..cce62f85 100644 --- a/docs/zh-CN/source-generators/cqrs-handler-registry-generator.md +++ b/docs/zh-CN/source-generators/cqrs-handler-registry-generator.md @@ -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 / descriptor,registrar 会把这些 request invoker 元数据预先登记到 dispatcher 缓存 +4. 若生成注册器同时提供 stream invoker provider / descriptor,runtime 也会优先消费对应的 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` 模块族入口见: