diff --git a/GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs index 30208feb..de3c7b92 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs @@ -16,6 +16,7 @@ using GFramework.Core.Logging; using GFramework.Cqrs.Abstractions.Cqrs; using MediatR; using Microsoft.Extensions.DependencyInjection; +using GeneratedMediator = Mediator.Mediator; namespace GFramework.Cqrs.Benchmarks.Messaging; @@ -27,8 +28,10 @@ public class NotificationBenchmarks { private MicrosoftDiContainer _container = null!; private ICqrsRuntime _runtime = null!; - private ServiceProvider _serviceProvider = null!; - private IPublisher _publisher = null!; + private ServiceProvider _mediatrServiceProvider = null!; + private ServiceProvider _mediatorServiceProvider = null!; + private IPublisher _mediatrPublisher = null!; + private GeneratedMediator _mediator = null!; private BenchmarkNotification _notification = null!; /// @@ -67,23 +70,26 @@ public class NotificationBenchmarks _container, LoggerFactoryResolver.Provider.CreateLogger(nameof(NotificationBenchmarks))); - _serviceProvider = BenchmarkHostFactory.CreateMediatRServiceProvider( + _mediatrServiceProvider = BenchmarkHostFactory.CreateMediatRServiceProvider( services => services.AddSingleton, BenchmarkNotificationHandler>(), typeof(NotificationBenchmarks), static candidateType => candidateType == typeof(BenchmarkNotificationHandler), ServiceLifetime.Singleton); - _publisher = _serviceProvider.GetRequiredService(); + _mediatrPublisher = _mediatrServiceProvider.GetRequiredService(); + + _mediatorServiceProvider = BenchmarkHostFactory.CreateMediatorServiceProvider(configure: null); + _mediator = _mediatorServiceProvider.GetRequiredService(); _notification = new BenchmarkNotification(Guid.NewGuid()); } /// - /// 释放 MediatR 对照组使用的 DI 宿主。 + /// 释放 MediatR 与 `Mediator` 对照组使用的 DI 宿主。 /// [GlobalCleanup] public void Cleanup() { - BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider); + BenchmarkCleanupHelper.DisposeAll(_container, _mediatrServiceProvider, _mediatorServiceProvider); } /// @@ -101,7 +107,16 @@ public class NotificationBenchmarks [Benchmark] public Task PublishNotification_MediatR() { - return _publisher.Publish(_notification, CancellationToken.None); + return _mediatrPublisher.Publish(_notification, CancellationToken.None); + } + + /// + /// 通过 `Mediator` source-generated concrete mediator 发布 notification,作为高性能对照组。 + /// + [Benchmark] + public ValueTask PublishNotification_Mediator() + { + return _mediator.Publish(_notification, CancellationToken.None); } /// @@ -110,6 +125,7 @@ public class NotificationBenchmarks /// 通知标识。 public sealed record BenchmarkNotification(Guid Id) : GFramework.Cqrs.Abstractions.Cqrs.INotification, + Mediator.INotification, MediatR.INotification; /// @@ -117,6 +133,7 @@ public class NotificationBenchmarks /// public sealed class BenchmarkNotificationHandler : GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler, + Mediator.INotificationHandler, MediatR.INotificationHandler { /// @@ -127,6 +144,16 @@ public class NotificationBenchmarks return ValueTask.CompletedTask; } + /// + /// 处理 NuGet `Mediator` notification。 + /// + ValueTask Mediator.INotificationHandler.Handle( + BenchmarkNotification notification, + CancellationToken cancellationToken) + { + return Handle(notification, cancellationToken); + } + /// /// 处理 MediatR notification。 /// diff --git a/GFramework.Cqrs.Benchmarks/README.md b/GFramework.Cqrs.Benchmarks/README.md index f589a0e3..2cb2d387 100644 --- a/GFramework.Cqrs.Benchmarks/README.md +++ b/GFramework.Cqrs.Benchmarks/README.md @@ -29,7 +29,7 @@ - `Messaging/StreamInvokerBenchmarks.cs` - direct handler、`GFramework.Cqrs` reflection runtime、handwritten generated-invoker runtime 与 `MediatR` 的 stream 完整枚举对比 - `Messaging/NotificationBenchmarks.cs` - - `GFramework.Cqrs` runtime 与 `MediatR` 的单处理器 notification publish 对比 + - `GFramework.Cqrs` runtime、NuGet `Mediator` source-generated concrete path 与 `MediatR` 的单处理器 notification publish 对比 - `Messaging/StreamingBenchmarks.cs` - direct handler、已接上 handwritten generated stream invoker provider 的 `GFramework.Cqrs` runtime 与 `MediatR` 的 stream request 完整枚举对比 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 59291782..71ae65d5 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,13 @@ CQRS 迁移与收敛。 ## 当前恢复点 -- 恢复点编号:`CQRS-REWRITE-RP-110` +- 恢复点编号:`CQRS-REWRITE-RP-111` - 当前阶段:`Phase 8` - 当前 PR 锚点:`PR #341` - 当前结论: + - 当前 `RP-111` 已继续沿用 `$gframework-batch-boot 50`,并按 skill 规则重新以 `origin/main` 作为基线复核:`origin/main` = `7ca21af9`(`2026-05-08 16:12:20 +0800`),本地 `main` = `c2d22285` 已落后,当前分支 `feat/cqrs-optimization` 与 `origin/main` 的累计 branch diff 为 `0 files / 0 lines`;基于“上下文预算优先、单批可评审边界次之”的停止规则,本轮选择 `NotificationBenchmarks` 这一条仍缺 `Mediator` concrete runtime 对照的单模块 benchmark 切片,而不是为了对称性继续扩展 notification runtime seam + - `NotificationBenchmarks` 现已从双方对照扩成三方对照:新增 NuGet `Mediator` source-generated concrete runtime 宿主与 `PublishNotification_Mediator()`,`BenchmarkNotification` / `BenchmarkNotificationHandler` 也同步接上 `Mediator` 的 notification 合同;当前 short-job 基线约为 `Mediator` `1.108 ns / 0 B`、`MediatR` `97.173 ns / 416 B`、`GFramework.Cqrs` `291.582 ns / 392 B` + - 本轮只把“notification publish 的高性能外部对照”补齐到 benchmark 层,而没有直接新增 generated notification invoker/provider 或 runtime 语义调整;原因是 notification dispatch 现有反射委托本就只在首次命中时缓存,继续加一层 provider 对 steady-state publish 的收益信号不如先把 `Mediator` concrete runtime 对照补齐来得清晰 - 当前 `RP-110` 已再次使用 `$gframework-pr-review` 复核 `PR #341` latest-head review:`BenchmarkHostFactory` 的 legacy runtime alias 防守式类型检查、benchmark 宿主定向 generated registry 激活、以及 `CqrsDispatcher.SendAsync(...)` 的 faulted `ValueTask` 失败语义在当前 head 均已实质收口;本轮仅继续接受仍然成立的 CodeRabbit nitpick,为 `SendAsync_Should_Return_Faulted_ValueTask_When_Handler_Is_Missing()` 补齐 `HasRegistration(...)` / `GetAll(...)` 防御性 mock,并删除 trace 中重复 `本轮权威验证` 的 `本轮下一步` 段落 - 当前 `RP-109` 已使用 `$gframework-pr-review` 复核 `PR #341` latest-head review:benchmark 宿主改为定向激活当前场景的 generated registry,避免同一 benchmark 程序集里的其他 registry 扩大冻结服务索引与 `HasRegistration` 基线;`BenchmarkHostFactory` 为 legacy runtime alias 注册补齐防守式类型检查与 stream lifetime 运行时注释;`CqrsDispatcher.SendAsync(...)` 在保留 direct-return 热路径的同时恢复 faulted `ValueTask` 失败语义,并补齐 generated registry 定向接线与 request fault 语义回归测试;`.agents/skills/gframework-batch-boot/SKILL.md` 的 MD005 缩进也已顺手修正 - `GFramework.Cqrs` 已完成对外部 `Mediator` 的生产级替代,当前主线已从“是否可替代”转向“仓库内部收口与能力深化顺序” @@ -114,7 +117,7 @@ CQRS 迁移与收敛。 - 若后续新增 benchmark / example / tooling 项目但未同步校验发布面,solution 级 `dotnet pack` 仍可能在 tag 发布前才暴露异常包 - `RequestStartupBenchmarks` 为了量化真正的单次 cold-start,引入了 `InvocationCount=1` / `UnrollFactor=1` 的专用 job;该配置会触发 BenchmarkDotNet 的 `MinIterationTime` 提示,后续若要做稳定基线比较,还需要决定是否引入批量外层循环或自定义 cold-start harness - 当前 benchmark 宿主仍刻意保持“单根容器最小宿主”模型;若要公平比较 `Scoped` handler 生命周期,需要先引入显式 scope 创建与 scope 内首次解析的对照基线 -- 当前 `Mediator` 对照组仅先接入 steady-state request;若要把 `Transient` / `Scoped` 生命周期矩阵也纳入同一组对照,需要按 `Mediator` 官方 benchmark 的做法拆分 compile-time lifetime build config,而不是在同一编译产物里混用多个 lifetime +- 当前 `Mediator` concrete runtime 对照已覆盖 steady-state request 与单处理器 notification publish;若要把 `Transient` / `Scoped` 生命周期矩阵、stream 生命周期矩阵或多处理器 notification publish 也纳入同一组对照,需要按 `Mediator` 官方 benchmark 的做法拆分 compile-time lifetime / 场景配置,而不是在同一编译产物里混用多个 runtime 变量 - 当前 stream 生命周期矩阵尚未接入 `Mediator` concrete runtime;若要继续对齐 `Mediator` 官方 benchmark 的 compile-time lifetime 设计,需要为 stream 场景补专门的 build-time 配置,而不是在当前统一宿主里临时拼接 - `BenchmarkDotNet.Artifacts/` 现已加入仓库忽略规则;若后续确实需要提交新的基准报告,应显式挑选结果文件或改走文档归档,而不是直接纳入整个生成目录 - 当前 `GFramework.Cqrs` request steady-state 仍慢于 `MediatR`;在“至少超过反射版 `MediatR`”这个阶段目标达成前,任何相关改动都不能只看功能 build/test 结果,必须附带 benchmark 回归数据 @@ -128,6 +131,16 @@ CQRS 迁移与收敛。 ## 最近权威验证 +- `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 "*NotificationBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1` + - 结果:通过 + - 备注:notification publish 三方对照当前约为 `Mediator` `1.108 ns / 0 B`、`MediatR` `97.173 ns / 416 B`、`GFramework.Cqrs` `291.582 ns / 392 B` +- `python3 scripts/license-header.py --check --paths GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs GFramework.Cqrs.Benchmarks/README.md` + - 结果:通过 +- `git diff --check` + - 结果:通过 + - 备注:仅剩 `GFramework.sln` 的历史 CRLF 提示,无本轮新增 diff 格式问题 - `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release` - 结果:通过,`0 warning / 0 error` - 备注:并行验证首轮曾因 `build` 与 `test` 同时写入同一输出 DLL 触发 `MSB3026` 单次复制重试;改为串行重跑同一命令后稳定通过 @@ -335,9 +348,10 @@ CQRS 迁移与收敛。 ## 下一推荐步骤 -1. 当前 turn 已到新的自然批次边界;本次提交后应停止,并在新的 turn 里从 `RP-108` 恢复点继续,而不是在本轮继续启动新的 benchmark 宿主或 runtime 热点切片 -2. 若下一轮继续沿用 `$gframework-batch-boot` 且优先处理性能,先看 notification publish 或更高价值的 request dispatch 常量开销热点,而不是继续堆同层级 benchmark 宿主补齐 -3. 若 benchmark 对照需要继续贴近 `Mediator` 官方设计,再评估 `Mediator` 的 compile-time lifetime / stream 对照矩阵,或给 stream 引入 scoped host 基线,而不是回头重试已被 benchmark 否决的 `GetAll(Type)` 零行为探测方案 +1. 当前 turn 已到新的自然批次边界;本次提交后应停止,并在新的 turn 里从 `RP-111` 恢复点继续,而不是在本轮继续堆叠 notification 基准或 runtime 热点切片 +2. 若下一轮继续沿用 `$gframework-batch-boot` 且优先处理性能,优先从多处理器 notification publish、publisher strategy,或更高价值的 request dispatch 常量开销热点里选一块,而不是继续做同层级 benchmark 宿主补齐 +3. 若继续沿着 notification 线推进,优先评估多处理器 publish、publisher strategy 或异常/取消语义的对照,而不是新增 generated notification invoker/provider 这一类 steady-state 收益信号偏弱的 runtime seam +4. 若 benchmark 对照需要继续贴近 `Mediator` 官方设计,再评估 `Mediator` 的 compile-time lifetime / stream 对照矩阵,或给 stream 引入 scoped host 基线,而不是回头重试已被 benchmark 否决的 `GetAll(Type)` 零行为探测方案 ## 活跃文档 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 d2219d2e..0f306672 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,37 @@ ## 2026-05-08 +### 阶段:notification publish 补齐 `Mediator` concrete runtime 对照(CQRS-REWRITE-RP-111) + +- 延续 `$gframework-batch-boot 50`,本轮重新按 skill 规则复核 branch diff 基线与容量: + - `origin/main` = `7ca21af9`,提交时间 `2026-05-08 16:12:20 +0800` + - 本地 `main` = `c2d22285`,已落后于 remote-tracking ref,因此不作为本轮 baseline + - 当前分支 `feat/cqrs-optimization` 与 `origin/main` 的累计 branch diff 为 `0 files / 0 lines` + - 当前工作树干净,且上一个自然批次 `RP-110` 已并入 `origin/main`;因此本轮不是“续做未提交热路径”,而是基于 active topic 重新选择下一块低风险 CQRS benchmark 切片 +- 本轮接受的只读探索结论: + - `NotificationBenchmarks` 仍停留在 `GFramework.Cqrs` vs `MediatR` 的双方对照,缺少 request steady-state 已具备的 `Mediator` concrete runtime 高性能参照物 + - 对 notification 路径直接补 generated invoker/provider 的性价比不高:dispatcher 当前对 notification 反射委托已按消息类型弱缓存,steady-state publish 的主要差距不在“每次都反射” + - 因此本轮更高信号、边界更清晰的切片是先补 benchmark 对照口径,而不是为了对称性新增一层 runtime seam +- 本轮主线程决策: + - 在 `GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs` 新增 `Mediator` concrete runtime 宿主、`PublishNotification_Mediator()` benchmark 方法,以及对应的 `Mediator.INotification` / `Mediator.INotificationHandler` 合同实现 + - 保持现有 `GFramework.Cqrs` 与 `MediatR` notification publish 路径不变,只扩充对照组,确保这批仍然是单模块、低风险、可直接评审的 benchmark 收口 + - 更新 `GFramework.Cqrs.Benchmarks/README.md` 与 active tracking,使 notification 场景的公开说明和恢复入口都反映新的三方对照事实 +- 本轮权威验证: + - `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 "*NotificationBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1` + - 结果:通过 + - 备注:notification publish 三方对照当前约为 `Mediator` `1.108 ns / 0 B`、`MediatR` `97.173 ns / 416 B`、`GFramework.Cqrs` `291.582 ns / 392 B` + - `python3 scripts/license-header.py --check --paths GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs GFramework.Cqrs.Benchmarks/README.md` + - 结果:通过 + - `git diff --check` + - 结果:通过 + - 备注:仅剩 `GFramework.sln` 的历史 CRLF 提示,无本轮新增 diff 格式问题 +- 本轮结论: + - notification 场景现在也拥有了与 request steady-state 对称的 `Mediator` concrete runtime 参照物,后续再讨论 `notification publisher` 策略或 runtime 热点时,不再只能拿 `MediatR` 做外部对照 + - 当前最值得保留的结论不是“立刻给 notification 也上 generated invoker/provider”,而是 `GFramework.Cqrs` 单处理器 publish 相对 `Mediator` 与 `MediatR` 的量级差距已经被量化出来,可为后续是否继续压 notification 路径提供依据 + - 本轮到这里属于新的自然批次边界;下一轮若继续沿用 `$gframework-batch-boot 50`,更适合从多处理器 publish / publisher strategy 或更高价值的 request 常量开销热点里再选一块,而不是在同一 turn 里继续堆 notification 基准扩展 + ### 阶段:PR #341 latest-head review 尾声收口(CQRS-REWRITE-RP-110) - 再次使用 `$gframework-pr-review` 抓取 `PR #341` latest-head review,确认当前 open thread 已收敛到: