diff --git a/GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs index 5ae7e606..431fa359 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs @@ -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(); } } } diff --git a/GFramework.Cqrs/README.md b/GFramework.Cqrs/README.md index 2d205335..78a051d4 100644 --- a/GFramework.Cqrs/README.md +++ b/GFramework.Cqrs/README.md @@ -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(); ``` diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md index 7de71bf2..f1f46ada 100644 --- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md +++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md @@ -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 review:CodeRabbit 当前仍成立的是 `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` diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md index f01a947a..0e1361e3 100644 --- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md +++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md @@ -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` diff --git a/docs/zh-CN/core/cqrs.md b/docs/zh-CN/core/cqrs.md index 7f9612fa..33056778 100644 --- a/docs/zh-CN/core/cqrs.md +++ b/docs/zh-CN/core/cqrs.md @@ -140,7 +140,6 @@ container.UseSequentialNotificationPublisher(); ```csharp using GFramework.Cqrs.Extensions; -using GFramework.Cqrs.Notification; container.UseTaskWhenAllNotificationPublisher(); ```