fix(cqrs): 收口 PR342 审查遗留问题

- 修复 NotificationFanOutBenchmarks 中 MediatR handler 绕过 HandleCore 的对照偏差

- 更新 README 与中文文档中的 notification publisher 示例和表格格式

- 同步 cqrs-rewrite tracking 与 trace 到 PR #342 审查恢复点和最新验证结果
This commit is contained in:
gewuyou 2026-05-08 19:29:45 +08:00
parent 4121e12909
commit 59ceb06f2d
5 changed files with 56 additions and 16 deletions

View File

@ -230,7 +230,7 @@ public class NotificationFanOutBenchmarks
BenchmarkNotification notification,
CancellationToken cancellationToken)
{
return Task.CompletedTask;
return HandleCore(notification, cancellationToken).AsTask();
}
}
@ -268,7 +268,7 @@ public class NotificationFanOutBenchmarks
BenchmarkNotification notification,
CancellationToken cancellationToken)
{
return Task.CompletedTask;
return HandleCore(notification, cancellationToken).AsTask();
}
}
@ -306,7 +306,7 @@ public class NotificationFanOutBenchmarks
BenchmarkNotification notification,
CancellationToken cancellationToken)
{
return Task.CompletedTask;
return HandleCore(notification, cancellationToken).AsTask();
}
}
@ -344,7 +344,7 @@ public class NotificationFanOutBenchmarks
BenchmarkNotification notification,
CancellationToken cancellationToken)
{
return Task.CompletedTask;
return HandleCore(notification, cancellationToken).AsTask();
}
}
}

View File

@ -128,11 +128,12 @@ var playerId = await this.SendAsync(new CreatePlayerCommand(new CreatePlayerInpu
- 若容器在 runtime 创建前已显式注册 `INotificationPublisher`,默认 runtime 会复用该策略;未注册时回退到内置 `SequentialNotificationPublisher`
- 内置 notification publisher 的推荐选择如下:
| 策略 | 推荐场景 | 执行顺序 | 失败语义 | 备注 |
| --- | --- | --- | --- | --- |
| `SequentialNotificationPublisher` | 需要保持容器顺序,且希望首个失败立即停止后续分发 | 保证按容器解析顺序逐个执行 | 首个处理器抛出异常时立即停止 | 也是默认回退策略 |
| `TaskWhenAllNotificationPublisher` | 需要让全部处理器并行完成,并在结束后统一观察失败或取消 | 不保证顺序 | 不会在首个失败时停止其余处理器;会聚合最终异常或取消结果 | 更适合语义补齐,不是性能开关 |
| `UseNotificationPublisher(...)` 自定义实例 | 需要接入仓库外的自定义策略或第三方策略 | 取决于具体实现 | 取决于具体实现 | 仅在内置顺序 / 并行策略都不满足时使用 |
| 策略 | 推荐场景 | 执行顺序 | 失败语义 | 备注 |
| --- | --- | --- | --- | --- |
| `SequentialNotificationPublisher` | 需要保持容器顺序,且希望首个失败立即停止后续分发 | 保证按容器解析顺序逐个执行 | 首个处理器抛出异常时立即停止 | 也是默认回退策略 |
| `TaskWhenAllNotificationPublisher` | 需要让全部处理器并行完成,并在结束后统一观察失败或取消 | 不保证顺序 | 不会在首个失败时停止其余处理器;会聚合最终异常或取消结果 | 更适合语义补齐,不是性能开关 |
| `UseNotificationPublisher(...)` 自定义实例 | 需要接入仓库外的自定义策略或第三方策略 | 取决于具体实现 | 取决于具体实现 | 仅在内置顺序 / 并行策略都不满足时使用 |
- 若只是为了降低 fixed fan-out publish 的 steady-state 成本,当前 benchmark 并不表明 `TaskWhenAllNotificationPublisher` 会优于默认顺序发布器;它更适合你需要“等待全部处理器完成并统一观察失败”的场景。
如果你需要显式保留默认顺序语义,也可以在组合根里直接声明:
@ -147,7 +148,6 @@ container.UseSequentialNotificationPublisher();
```csharp
using GFramework.Cqrs.Extensions;
using GFramework.Cqrs.Notification;
container.UseTaskWhenAllNotificationPublisher();
```

View File

@ -7,10 +7,14 @@ CQRS 迁移与收敛。
## 当前恢复点
- 恢复点编号:`CQRS-REWRITE-RP-117`
- 恢复点编号:`CQRS-REWRITE-RP-118`
- 当前阶段:`Phase 8`
- 当前 PR 锚点:`PR #341`
- 当前 PR 锚点:`PR #342`
- 当前结论:
- 当前 `RP-118` 已使用 `$gframework-pr-review` 复核 `PR #342` latest-head reviewCodeRabbit 当前仍成立的是 `NotificationFanOutBenchmarks` 中 MediatR 分支绕过共享 `HandleCore(...)``GFramework.Cqrs/README.md` 的 MD058 表格空行、以及恢复文档的 PR 锚点与 fan-out 历史值表述Greptile 额外指出的 `UseTaskWhenAllNotificationPublisher()` 示例多余 `using GFramework.Cqrs.Notification;` 也在本轮一并收口
- 本轮不改 `GFramework.Cqrs` runtime 语义,只让 benchmark 的 MediatR handler 与其余对照分支共用同一组空值 / 取消检查,并把 README、中文文档与 `cqrs-rewrite` 恢复文档同步到当前 PR #342 上下文
- 本轮按 `NotificationFanOutBenchmarks` short-job 复跑确认,对称化 MediatR handler 后当前 fixed `4 handler` fan-out 结果约为 `Mediator` `3.598 ns / 0 B`、baseline `7.033 ns / 0 B``MediatR` `257.533 ns / 1256 B``GFramework.Cqrs` 顺序 `409.557 ns / 408 B``TaskWhenAll` `484.531 ns / 496 B`
- `RP-117` 留在“最近权威验证”中的固定 `4 handler` fan-out 数值属于早期 benchmark 记录,因此本轮选择显式补上 `历史基线RP-112` 标注,而不是把历史验证段落改写成新的 benchmark 结果,避免混淆恢复轨迹
- 当前 `RP-117` 继续沿用 `$gframework-batch-boot 50`,但没有继续把 batch 推回 request dispatch 热路径:本轮先试了一刀“按运行时类型缓存 `IContextAware` 判定”的 dispatcher 微优化,随后按 `RequestBenchmarks` / `RequestLifetimeBenchmarks` 复跑确认 steady-state request 反而回落到约 `71.824 ns`,因此这组运行时代码已在同轮完全撤回,不保留负收益热点实验
- 这一批改为只收口 notification publisher 的采用文档:`GFramework.Cqrs/README.md``docs/zh-CN/core/cqrs.md` 现在把 `Sequential` / `TaskWhenAll` / 自定义 publisher 三条策略放进同一张选择矩阵,明确 `TaskWhenAll` 的价值是“并行完成 + 聚合失败”,而不是 fixed fan-out publish 的性能升级开关
- 当前分支相对 `origin/main``7ca21af9`, `2026-05-08 16:12:20 +0800`)的累计 branch diff 仍约为 `12 files`,远低于 `$gframework-batch-boot 50` 的停止阈值;因此这批继续保持 notification 采用边界内的低风险、可评审文档切片
@ -155,7 +159,17 @@ CQRS 迁移与收敛。
- 结果:通过,`0 warning / 0 error`
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --filter "*NotificationFanOutBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
- 结果:通过
- 备注:固定 `4 handler` notification fan-out 对照当前约为 baseline `8.302 ns / 0 B``Mediator` `4.314 ns / 0 B``MediatR` `230.304 ns / 1256 B``GFramework.Cqrs` `434.413 ns / 408 B`
- 备注:本轮对称化 MediatR handler 后fixed `4 handler` fan-out 对照约为 `Mediator` `3.598 ns / 0 B`、baseline `7.033 ns / 0 B``MediatR` `257.533 ns / 1256 B``GFramework.Cqrs` 顺序 `409.557 ns / 408 B``TaskWhenAll` `484.531 ns / 496 B`
- `python3 scripts/license-header.py --check --paths GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs GFramework.Cqrs/README.md docs/zh-CN/core/cqrs.md ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md`
- 结果:通过
- `git diff --check`
- 结果:通过
- 备注:仅剩 `GFramework.sln` 的历史 CRLF 提示,无本轮新增 diff 格式问题
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --filter "*NotificationFanOutBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
- 结果:通过
- 备注:历史基线(`RP-112`)固定 `4 handler` notification fan-out 对照约为 baseline `8.302 ns / 0 B``Mediator` `4.314 ns / 0 B``MediatR` `230.304 ns / 1256 B``GFramework.Cqrs` `434.413 ns / 408 B`
- `python3 scripts/license-header.py --check --paths GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs GFramework.Cqrs.Benchmarks/README.md`
- 结果:通过
- `git diff --check`
@ -183,7 +197,7 @@ CQRS 迁移与收敛。
- 备注:仅剩 `GFramework.sln` 的历史 CRLF 提示,无本轮新增 diff 格式问题
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
- 结果:通过
- 备注:确认当前分支对应 `PR #341`latest-head 当前仍显示 `CodeRabbit 2` / `Greptile 2` open thread但其中 `BenchmarkHostFactory` / benchmark registry / faulted `ValueTask` 三类运行时 thread 已在本地失效,当前仅剩测试 mock 脆弱性与 trace 冗余仍值得继续收口
- 备注:确认当前分支对应 `PR #342`latest-head 当前显示 `CodeRabbit 4` / `Greptile 3` open thread其中真正仍成立的是 benchmark handler 对称性、README / 中文文档示例与恢复文档锚点漂移,其余历史 thread 需要按当前 head 继续甄别
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`
- 结果:通过
- 备注:确认当前分支对应 `PR #340`latest-head 当前显示 `CodeRabbit 2` / `Greptile 2` open thread`CTRF` 报告中唯一失败测试为 `CreateStream_Should_Throw_When_Stream_Pipeline_Behavior_Context_Does_Not_Implement_IArchitectureContext`

View File

@ -2,6 +2,33 @@
## 2026-05-08
### 阶段PR #342 latest-head review 收口CQRS-REWRITE-RP-118
- 使用 `$gframework-pr-review` 抓取 `feat/cqrs-optimization` 当前公开 PR并确认当前锚点已从 `PR #341` 更新为 `PR #342`
- 本轮 latest-head review 结论:
- `CodeRabbit` 当前仍成立的是 `NotificationFanOutBenchmarks.cs` 中 MediatR 显式 `Handle(...)` 直接返回 `Task.CompletedTask`,导致该对照组绕过共享 `HandleCore(...)` 的空值 / 取消校验
- `CodeRabbit``ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` 的两条评论也成立:当前恢复点锚点仍写 `PR #341`,且“最近权威验证”里的 fan-out 数值属于更早轮次,需要显式标注历史来源
- `Greptile` 额外指出 `GFramework.Cqrs/README.md``docs/zh-CN/core/cqrs.md``UseTaskWhenAllNotificationPublisher()` 示例包含多余 `using GFramework.Cqrs.Notification;`;这条在当前 head 仍成立
- MegaLinter 仍报告 `dotnet-format` restore 失败,但这属于 CI 环境 restore 噪声,不是当前 diff 的格式违规README 的 MD058 空行问题仍需在本地直接修复
- 本轮主线程决策:
- 让 `NotificationFanOutBenchmarks` 的四个 MediatR handler 显式转发到 `HandleCore(notification, cancellationToken).AsTask()`,保持与 baseline、`GFramework.Cqrs` 和 NuGet `Mediator` 分支一致的前置检查
- 在 `GFramework.Cqrs/README.md` 修复表格前后空行,并删除 README / 中文文档中 `UseTaskWhenAllNotificationPublisher()` 示例的多余 `using`
- 把 `cqrs-rewrite` tracking 当前恢复点推进到 `RP-118`,同步 `PR #342` 锚点,并把早期 fan-out 数值显式标成 `历史基线RP-112`
- 本轮权威验证:
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
- 结果:通过
- 备注:确认当前分支对应 `PR #342`CodeRabbit 当前 `4` 条 actionable comments 与 Greptile `3` 条 open thread 已作为本轮本地复核输入
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --filter "*NotificationFanOutBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
- 结果:通过
- 备注:本轮对称化 MediatR handler 后fixed `4 handler` fan-out 对照约为 `Mediator` `3.598 ns / 0 B`、baseline `7.033 ns / 0 B``MediatR` `257.533 ns / 1256 B``GFramework.Cqrs` 顺序 `409.557 ns / 408 B``TaskWhenAll` `484.531 ns / 496 B`
- `python3 scripts/license-header.py --check --paths GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs GFramework.Cqrs/README.md docs/zh-CN/core/cqrs.md ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md`
- 结果:通过
- `git diff --check`
- 结果:通过
- 备注:仅剩 `GFramework.sln` 的历史 CRLF 提示,无本轮新增 diff 格式问题
### 阶段notification publisher 采用矩阵文档收口CQRS-REWRITE-RP-117
- 延续 `$gframework-batch-boot 50`,本轮没有继续把自动批处理推到新的 runtime seam而是先按 tracking 建议复核“notification 线是否还缺采用边界文档”:
@ -136,7 +163,7 @@
- 结果:通过,`0 warning / 0 error`
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --filter "*NotificationFanOutBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
- 结果:通过
- 备注:固定 `4 handler` notification fan-out 对照当前约为 baseline `8.302 ns / 0 B``Mediator` `4.314 ns / 0 B``MediatR` `230.304 ns / 1256 B``GFramework.Cqrs` `434.413 ns / 408 B`
- 备注:历史基线(`RP-112`固定 `4 handler` notification fan-out 对照约为 baseline `8.302 ns / 0 B``Mediator` `4.314 ns / 0 B``MediatR` `230.304 ns / 1256 B``GFramework.Cqrs` `434.413 ns / 408 B`
- `python3 scripts/license-header.py --check --paths GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs GFramework.Cqrs.Benchmarks/README.md`
- 结果:通过
- `git diff --check`

View File

@ -140,7 +140,6 @@ container.UseSequentialNotificationPublisher();
```csharp
using GFramework.Cqrs.Extensions;
using GFramework.Cqrs.Notification;
container.UseTaskWhenAllNotificationPublisher();
```