mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-12 05:08:58 +08:00
docs(cqrs): 收紧生成器与通知策略说明
- 更新 CQRS 栏目中的 generated invoker、fallback 分层与 notification publisher 选择边界说明 - 对齐生成器专题页与当前 runtime 注册和分发实现的实际行为
This commit is contained in:
parent
3b2e6899d5
commit
d9e47abdb6
@ -118,6 +118,7 @@ var playerId = await architecture.Context.SendRequestAsync(
|
|||||||
- 已解析处理器按容器顺序逐个执行
|
- 已解析处理器按容器顺序逐个执行
|
||||||
- 首个处理器抛出异常时立即停止后续分发
|
- 首个处理器抛出异常时立即停止后续分发
|
||||||
- 如果容器在 runtime 创建前已显式注册 `INotificationPublisher`,默认 runtime 会复用该策略;未注册时回退到内置 `SequentialNotificationPublisher`
|
- 如果容器在 runtime 创建前已显式注册 `INotificationPublisher`,默认 runtime 会复用该策略;未注册时回退到内置 `SequentialNotificationPublisher`
|
||||||
|
- 默认 runtime 只消费一个 `INotificationPublisher`;如果容器里已经存在该注册,再调用 `UseNotificationPublisher*` 系列扩展会直接报错,而不是按“后注册覆盖前注册”处理
|
||||||
|
|
||||||
如果你需要在组合根里明确表达“为什么选这条策略”,可以按下面的矩阵判断:
|
如果你需要在组合根里明确表达“为什么选这条策略”,可以按下面的矩阵判断:
|
||||||
|
|
||||||
@ -125,7 +126,7 @@ var playerId = await architecture.Context.SendRequestAsync(
|
|||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| `UseSequentialNotificationPublisher()` | 需要保持容器顺序,且希望首个失败立即停止 | 保证按容器顺序执行 | 首个处理器异常会中断后续处理器 | 这也是默认回退策略 |
|
| `UseSequentialNotificationPublisher()` | 需要保持容器顺序,且希望首个失败立即停止 | 保证按容器顺序执行 | 首个处理器异常会中断后续处理器 | 这也是默认回退策略 |
|
||||||
| `UseTaskWhenAllNotificationPublisher()` | 需要让全部处理器并行完成,再统一观察异常或取消 | 不保证顺序 | 不会在首个失败时中断其余处理器;全部结束后统一暴露结果 | 更适合语义补齐,不是性能优化开关 |
|
| `UseTaskWhenAllNotificationPublisher()` | 需要让全部处理器并行完成,再统一观察异常或取消 | 不保证顺序 | 不会在首个失败时中断其余处理器;全部结束后统一暴露结果 | 更适合语义补齐,不是性能优化开关 |
|
||||||
| `UseNotificationPublisher(...)` / `UseNotificationPublisher<TPublisher>()` | 需要接入自定义或第三方 publisher 策略 | 取决于实现 | 取决于实现 | 前者复用现成实例,后者让容器负责单例生命周期 |
|
| `UseNotificationPublisher(...)` / `UseNotificationPublisher<TPublisher>()` | 需要接入自定义或第三方 publisher 策略 | 取决于实现 | 取决于实现 | 前者复用现成实例,后者让容器负责单例生命周期;两者都要求容器此前尚未注册 `INotificationPublisher` |
|
||||||
|
|
||||||
如果你想在组合根里显式保留默认顺序语义,也可以直接写成:
|
如果你想在组合根里显式保留默认顺序语义,也可以直接写成:
|
||||||
|
|
||||||
@ -216,11 +217,12 @@ protected override void OnInitialize()
|
|||||||
2. 存在生成注册器时优先使用 `ICqrsHandlerRegistry`
|
2. 存在生成注册器时优先使用 `ICqrsHandlerRegistry`
|
||||||
3. 当生成注册器同时暴露 generated request invoker provider 时,runtime 会把 request/response 类型对对应的 descriptor 预先接线到 dispatcher 缓存,后续请求分发优先消费这些 generated request invoker 元数据
|
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
|
4. 当生成注册器同时暴露 generated stream invoker provider 时,runtime 会以同样方式优先消费 stream request 对应的 generated stream invoker descriptor;只有当前类型对未命中时,才回退到既有反射 stream binding
|
||||||
5. 生成注册器不可用时记录告警并回退到反射路径;只有“未命中 generated descriptor”才会走反射绑定,已命中的不兼容元数据会直接抛出异常
|
5. generated invoker 只覆盖 request 与 stream 两类单次分发元数据;notification handler 仍通过已注册的 `INotificationHandler<>` 集合和选定的 `INotificationPublisher` 参与分发,不存在对应的 generated notification invoker 通道
|
||||||
6. 当生成注册器携带 `CqrsReflectionFallbackAttribute` 元数据时,运行时会先完成生成注册器注册,再补剩余 handler
|
6. 生成注册器不可用时记录告警并回退到反射路径;只有“未命中 generated descriptor”才会走反射 binding 创建,已成功登记到缓存的类型对不会再回退到另一条 generated 通道
|
||||||
7. `CqrsReflectionFallbackAttribute` 可以同时携带 `Type[]` 和 `string[]` 两类清单;运行时会优先复用直接 `Type` 条目,只对名称条目做定向 `Assembly.GetType(...)` 查找
|
7. 当生成注册器携带 `CqrsReflectionFallbackAttribute` 元数据时,运行时会先完成生成注册器注册,再补剩余 handler
|
||||||
8. 只有旧版空 marker 或生成注册器不可用时,才会回到整程序集反射扫描
|
8. `CqrsReflectionFallbackAttribute` 可以同时携带 `Type[]` 和 `string[]` 两类清单;运行时会优先复用直接 `Type` 条目,只对名称条目做定向 `Assembly.GetType(...)` 查找
|
||||||
9. 同一程序集按稳定键去重,避免重复注册
|
9. 只有 fallback 元数据为空、仍是旧版空 marker 语义,或生成注册器整体不可用时,才会回到整程序集反射扫描
|
||||||
|
10. 同一程序集按稳定键去重,避免重复注册
|
||||||
|
|
||||||
换句话说,声明 fallback 特性本身不等于“整包反射扫描”。当前推荐理解是:生成注册器负责能静态表达的部分,fallback 只补它覆盖不到的 handler。
|
换句话说,声明 fallback 特性本身不等于“整包反射扫描”。当前推荐理解是:生成注册器负责能静态表达的部分,fallback 只补它覆盖不到的 handler。
|
||||||
|
|
||||||
@ -231,7 +233,7 @@ protected override void OnInitialize()
|
|||||||
- stream invoker provider / descriptor
|
- stream invoker provider / descriptor
|
||||||
- 面向 `CreateStream(...)` 触发的流式请求分发
|
- 面向 `CreateStream(...)` 触发的流式请求分发
|
||||||
|
|
||||||
两者的共同点都是“优先消费 generated invoker 元数据,未命中时保留既有反射绑定作为兜底”,而不是要求业务侧切换到另一套 runtime 入口。
|
两者的共同点都是“优先消费 generated invoker 元数据,未命中时保留既有反射绑定作为兜底”,而不是要求业务侧切换到另一套 runtime 入口。通知发布不在这组 generated invoker 能力里;它始终沿用 runtime 解析出的 handler 集合与当前 publisher 策略。
|
||||||
|
|
||||||
对接入方来说,更关键的 reader-facing 语义是:安装 `Cqrs.SourceGenerators` 后,不要求“所有 handler 都能被生成代码直接引用”才有收益。
|
对接入方来说,更关键的 reader-facing 语义是:安装 `Cqrs.SourceGenerators` 后,不要求“所有 handler 都能被生成代码直接引用”才有收益。
|
||||||
即使仍有 fallback,runtime 也会先消费 generated registry,再只对剩余 handler 做定向补扫;只有旧版 marker 语义或空 fallback 元数据才会退回整程序集扫描。
|
即使仍有 fallback,runtime 也会先消费 generated registry,再只对剩余 handler 做定向补扫;只有旧版 marker 语义或空 fallback 元数据才会退回整程序集扫描。
|
||||||
|
|||||||
@ -40,6 +40,7 @@ runtime 在注册 handlers 时优先走静态注册表;当运行时合同允
|
|||||||
这意味着运行时会先使用生成注册器完成可静态表达的映射;对 request 与 stream 分发来说,也会优先消费 generated invoker
|
这意味着运行时会先使用生成注册器完成可静态表达的映射;对 request 与 stream 分发来说,也会优先消费 generated invoker
|
||||||
descriptor。只有当前类型对没有 generated metadata,或 registry / fallback 无法覆盖时,才继续回到既有反射 binding 或补扫路径,而不是退回整程序集盲扫。
|
descriptor。只有当前类型对没有 generated metadata,或 registry / fallback 无法覆盖时,才继续回到既有反射 binding 或补扫路径,而不是退回整程序集盲扫。
|
||||||
如果这些 fallback handlers 本身仍可直接引用,生成器会优先发射 `typeof(...)` 形式的 fallback 元数据;当 runtime 允许同一程序集声明多个 fallback 特性实例时,mixed 场景也会拆成 `Type` 元数据和字符串元数据两段,进一步减少 runtime 再做字符串类型名回查的成本。
|
如果这些 fallback handlers 本身仍可直接引用,生成器会优先发射 `typeof(...)` 形式的 fallback 元数据;当 runtime 允许同一程序集声明多个 fallback 特性实例时,mixed 场景也会拆成 `Type` 元数据和字符串元数据两段,进一步减少 runtime 再做字符串类型名回查的成本。
|
||||||
|
这里的 generated invoker 只覆盖 `IRequestHandler<,>` 与 `IStreamRequestHandler<,>`。`INotificationHandler<>` 仍然只参与 registry / fallback 注册;通知分发本身继续由 runtime 解析出的 handler 集合和 `INotificationPublisher` 策略决定。
|
||||||
|
|
||||||
## 最小接入路径
|
## 最小接入路径
|
||||||
|
|
||||||
@ -87,9 +88,10 @@ RegisterCqrsHandlersFromAssemblies(
|
|||||||
2. 优先激活生成的 `ICqrsHandlerRegistry`
|
2. 优先激活生成的 `ICqrsHandlerRegistry`
|
||||||
3. 若生成注册器同时提供 request invoker provider / descriptor,registrar 会把这些 request invoker 元数据预先登记到 dispatcher 缓存
|
3. 若生成注册器同时提供 request invoker provider / descriptor,registrar 会把这些 request invoker 元数据预先登记到 dispatcher 缓存
|
||||||
4. 若生成注册器同时提供 stream invoker provider / descriptor,runtime 也会优先消费对应的 generated stream invoker 元数据;未命中时仍回退到既有反射 stream binding
|
4. 若生成注册器同时提供 stream invoker provider / descriptor,runtime 也会优先消费对应的 generated stream invoker 元数据;未命中时仍回退到既有反射 stream binding
|
||||||
5. 若生成元数据损坏、registry 不可激活,记录告警并回退到反射路径
|
5. generated invoker provider 不是独立入口;它只是让 dispatcher 在已知 `requestType + responseType` 类型对时优先命中编译期 descriptor,未命中时仍保持原有 runtime 分发入口
|
||||||
6. 若存在 `CqrsReflectionFallbackAttribute`,优先按其中携带的 `Type` 或类型名补扫剩余 handler;若元数据为空或只保留 marker 语义,则退回整程序集补扫
|
6. 若生成元数据损坏、registry 不可激活,记录告警并回退到反射路径
|
||||||
7. 同一程序集按稳定键去重,避免重复注册
|
7. 若存在 `CqrsReflectionFallbackAttribute`,优先按其中携带的 `Type` 或类型名补扫剩余 handler;只有元数据为空、只保留 marker 语义,或 registry 整体不可用时,才退回整程序集补扫
|
||||||
|
8. 同一程序集按稳定键去重,避免重复注册
|
||||||
|
|
||||||
这个行为由
|
这个行为由
|
||||||
[运行时注册流程测试](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs)
|
[运行时注册流程测试](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs)
|
||||||
@ -127,6 +129,8 @@ RegisterCqrsHandlersFromAssemblies(
|
|||||||
- 其余场景统一回退到字符串元数据,避免 mixed 场景漏注册
|
- 其余场景统一回退到字符串元数据,避免 mixed 场景漏注册
|
||||||
- 只有在 runtime 提供 `CqrsReflectionFallbackAttribute` 合同时,才允许发射依赖 fallback 的结果
|
- 只有在 runtime 提供 `CqrsReflectionFallbackAttribute` 合同时,才允许发射依赖 fallback 的结果
|
||||||
|
|
||||||
|
`fallback` 在这里表示“补齐生成注册器没有直接接线的剩余 handler”,不是“生成器一出现就重新扫描整个程序集”。只要 attribute 里已经带了明确 `Type` 或类型名,runtime 就会先走这份定向清单。
|
||||||
|
|
||||||
## 生成策略层级
|
## 生成策略层级
|
||||||
|
|
||||||
把这个生成器理解成“静态注册 or 整程序集扫描”的二选一,会低估它的收益。当前策略实际上分成四层:
|
把这个生成器理解成“静态注册 or 整程序集扫描”的二选一,会低估它的收益。当前策略实际上分成四层:
|
||||||
@ -141,6 +145,7 @@ RegisterCqrsHandlersFromAssemblies(
|
|||||||
- 只有前面几层都无法覆盖的剩余 handler,才交给 `CqrsReflectionFallbackAttribute`
|
- 只有前面几层都无法覆盖的剩余 handler,才交给 `CqrsReflectionFallbackAttribute`
|
||||||
|
|
||||||
这意味着安装生成器后,并不要求“所有 handler 都可直接引用”才有收益。很多只能部分静态表达的项目,仍然可以把大部分注册路径前移到编译期,再对少数复杂类型做定向补扫。
|
这意味着安装生成器后,并不要求“所有 handler 都可直接引用”才有收益。很多只能部分静态表达的项目,仍然可以把大部分注册路径前移到编译期,再对少数复杂类型做定向补扫。
|
||||||
|
其中 request / stream 的 generated invoker descriptor 只在前两类 runtime seam 同时存在、且当前 handler 能安全生成静态 invoker 时才会出现;否则对应请求仍然走已存在的反射 binding 创建路径,不会影响 registry 本身继续工作。
|
||||||
|
|
||||||
## 哪些场景通常不会直接退回整程序集扫描
|
## 哪些场景通常不会直接退回整程序集扫描
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user