mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-12 22:03:30 +08:00
Merge pull request #350 from GeWuYou/feat/cqrs-optimization
Feat/Add Mediator support to benchmark suites and update documentation
This commit is contained in:
commit
4837aa2a23
@ -275,15 +275,16 @@ internal static class BenchmarkHostFactory
|
||||
/// <param name="configure">补充当前场景的显式服务注册。</param>
|
||||
/// <returns>可直接解析 generated `Mediator.Mediator` 的 DI 宿主。</returns>
|
||||
/// <remarks>
|
||||
/// 当前 benchmark 只把 `Mediator` 作为单例 steady-state 对照组接入,
|
||||
/// 因为它的 lifetime 由 source generator 在编译期塑形;若后续需要 `Transient` / `Scoped` 矩阵,
|
||||
/// 应按 `Mediator` 官方 benchmark 的做法拆成独立 build config,而不是在同一编译产物里混用多个 lifetime。
|
||||
/// `Mediator` 的 DI lifetime 由 source generator 在编译期固定到整个位于当前项目中的生成产物上。
|
||||
/// 因此 benchmark 工程必须统一使用一套 compile-time 常量配置;这里显式收敛为 `Singleton`,
|
||||
/// 避免同一编译产物里混入多个 `AddMediator` lifetime 形状后,在 BenchmarkDotNet 自动生成宿主中触发
|
||||
/// “generated code lifetime 与 runtime options 不一致”的启动失败。
|
||||
/// </remarks>
|
||||
internal static ServiceProvider CreateMediatorServiceProvider(Action<IServiceCollection>? configure)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
configure?.Invoke(services);
|
||||
services.AddMediator();
|
||||
services.AddMediator(static options => options.ServiceLifetime = ServiceLifetime.Singleton);
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ using GeneratedMediator = Mediator.Mediator;
|
||||
namespace GFramework.Cqrs.Benchmarks.Messaging;
|
||||
|
||||
/// <summary>
|
||||
/// 对比单处理器 notification 在 GFramework.CQRS 与 MediatR 之间的 publish 开销。
|
||||
/// 对比单处理器 notification 在 GFramework.CQRS、NuGet `Mediator` 与 MediatR 之间的 publish 开销。
|
||||
/// </summary>
|
||||
[Config(typeof(Config))]
|
||||
public class NotificationBenchmarks
|
||||
@ -95,6 +95,7 @@ public class NotificationBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 GFramework.CQRS runtime 发布 notification。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 GFramework.CQRS publish 完成的值任务。</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ValueTask PublishNotification_GFrameworkCqrs()
|
||||
{
|
||||
@ -104,6 +105,7 @@ public class NotificationBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 MediatR 发布 notification,作为外部设计对照。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 MediatR publish 完成的任务。</returns>
|
||||
[Benchmark]
|
||||
public Task PublishNotification_MediatR()
|
||||
{
|
||||
@ -113,6 +115,7 @@ public class NotificationBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 `Mediator` source-generated concrete mediator 发布 notification,作为高性能对照组。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 `Mediator` publish 完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask PublishNotification_Mediator()
|
||||
{
|
||||
@ -129,7 +132,7 @@ public class NotificationBenchmarks
|
||||
MediatR.INotification;
|
||||
|
||||
/// <summary>
|
||||
/// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 notification handler。
|
||||
/// 同时实现 GFramework.CQRS、NuGet `Mediator` 与 MediatR 契约的最小 notification handler。
|
||||
/// </summary>
|
||||
public sealed class BenchmarkNotificationHandler :
|
||||
GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler<BenchmarkNotification>,
|
||||
|
||||
@ -123,6 +123,7 @@ public class NotificationFanOutBenchmarks
|
||||
/// <summary>
|
||||
/// 直接依次调用 4 个处理器,作为 fan-out dispatch 额外开销的 baseline。
|
||||
/// </summary>
|
||||
/// <returns>代表基线顺序调用 4 个处理器完成当前 notification 处理的值任务。</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
public async ValueTask PublishNotification_Baseline()
|
||||
{
|
||||
@ -135,6 +136,7 @@ public class NotificationFanOutBenchmarks
|
||||
/// <summary>
|
||||
/// 通过默认顺序发布器的 GFramework.CQRS runtime 发布固定 4 处理器的 notification。
|
||||
/// </summary>
|
||||
/// <returns>代表当前默认顺序发布器 publish 完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask PublishNotification_GFrameworkCqrsSequential()
|
||||
{
|
||||
@ -144,6 +146,7 @@ public class NotificationFanOutBenchmarks
|
||||
/// <summary>
|
||||
/// 通过内置 <c>Task.WhenAll(...)</c> 发布器的 GFramework.CQRS runtime 发布固定 4 处理器的 notification。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 <c>Task.WhenAll(...)</c> 发布器 publish 完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask PublishNotification_GFrameworkCqrsTaskWhenAll()
|
||||
{
|
||||
@ -153,6 +156,7 @@ public class NotificationFanOutBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 MediatR 发布固定 4 处理器的 notification,作为外部设计对照。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 MediatR publish 完成的任务。</returns>
|
||||
[Benchmark]
|
||||
public Task PublishNotification_MediatR()
|
||||
{
|
||||
@ -162,6 +166,7 @@ public class NotificationFanOutBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 `Mediator` source-generated concrete mediator 发布固定 4 处理器的 notification,作为高性能对照组。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 `Mediator` publish 完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask PublishNotification_Mediator()
|
||||
{
|
||||
|
||||
@ -104,6 +104,7 @@ public class RequestBenchmarks
|
||||
/// <summary>
|
||||
/// 直接调用 handler,作为 dispatch 额外开销的 baseline。
|
||||
/// </summary>
|
||||
/// <returns>代表基线 handler 完成当前 request 处理的值任务。</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ValueTask<BenchmarkResponse> SendRequest_Baseline()
|
||||
{
|
||||
@ -113,6 +114,7 @@ public class RequestBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 GFramework.CQRS runtime 发送 request。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 GFramework.CQRS request dispatch 完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask<BenchmarkResponse> SendRequest_GFrameworkCqrs()
|
||||
{
|
||||
@ -122,6 +124,7 @@ public class RequestBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 MediatR 发送 request,作为外部设计对照。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 MediatR request dispatch 完成的任务。</returns>
|
||||
[Benchmark]
|
||||
public Task<BenchmarkResponse> SendRequest_MediatR()
|
||||
{
|
||||
@ -131,6 +134,7 @@ public class RequestBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 `ai-libs/Mediator` 的 source-generated concrete mediator 发送 request,作为高性能对照组。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 `Mediator` request dispatch 完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask<BenchmarkResponse> SendRequest_Mediator()
|
||||
{
|
||||
@ -153,7 +157,7 @@ public class RequestBenchmarks
|
||||
public sealed record BenchmarkResponse(Guid Id);
|
||||
|
||||
/// <summary>
|
||||
/// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 request handler。
|
||||
/// 同时实现 GFramework.CQRS、NuGet `Mediator` 与 MediatR 契约的最小 request handler。
|
||||
/// </summary>
|
||||
public sealed class BenchmarkRequestHandler :
|
||||
GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>,
|
||||
|
||||
@ -116,6 +116,7 @@ public class RequestInvokerBenchmarks
|
||||
/// <summary>
|
||||
/// 直接调用最小 request handler,作为 dispatch 额外开销 baseline。
|
||||
/// </summary>
|
||||
/// <returns>代表基线 request handler 完成当前 request 处理的值任务。</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ValueTask<ReflectionBenchmarkResponse> SendRequest_Baseline()
|
||||
{
|
||||
@ -125,6 +126,7 @@ public class RequestInvokerBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 GFramework.CQRS 反射 request binding 路径发送 request。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 reflection request dispatch 完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask<ReflectionBenchmarkResponse> SendRequest_GFrameworkReflection()
|
||||
{
|
||||
@ -134,6 +136,7 @@ public class RequestInvokerBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 generated request invoker provider 预热后的 GFramework.CQRS runtime 发送 request。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 generated request dispatch 完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask<GeneratedBenchmarkResponse> SendRequest_GFrameworkGenerated()
|
||||
{
|
||||
@ -143,6 +146,7 @@ public class RequestInvokerBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 MediatR 发送 request,作为外部对照。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 MediatR request dispatch 完成的任务。</returns>
|
||||
[Benchmark]
|
||||
public Task<MediatRBenchmarkResponse> SendRequest_MediatR()
|
||||
{
|
||||
|
||||
@ -26,6 +26,10 @@ namespace GFramework.Cqrs.Benchmarks.Messaging;
|
||||
/// 当前矩阵覆盖 `Singleton`、`Scoped` 与 `Transient`。
|
||||
/// 其中 `Scoped` 会在每次 request 分发时显式创建并释放真实的 DI 作用域,
|
||||
/// 避免把 scoped handler 错误地压到根容器解析而扭曲生命周期对照。
|
||||
/// NuGet `Mediator` 的 DI lifetime 由 source generator 在编译期固定到整个 benchmark 项目,
|
||||
/// 不能在同一份生成产物里同时切换 `Singleton`、`Scoped` 与 `Transient`。
|
||||
/// 因此该矩阵当前只比较 `GFramework.Cqrs` 与 `MediatR` 的生命周期开销;`Mediator` 仍保留在其他
|
||||
/// steady-state / startup benchmark 中作为单一 compile-time 形状对照。
|
||||
/// </remarks>
|
||||
[Config(typeof(Config))]
|
||||
public class RequestLifetimeBenchmarks
|
||||
@ -151,6 +155,7 @@ public class RequestLifetimeBenchmarks
|
||||
/// <summary>
|
||||
/// 直接调用 handler,作为不同生命周期矩阵下的 dispatch 额外开销 baseline。
|
||||
/// </summary>
|
||||
/// <returns>代表基线 request handler 完成当前 request 处理的值任务。</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ValueTask<BenchmarkResponse> SendRequest_Baseline()
|
||||
{
|
||||
@ -160,6 +165,7 @@ public class RequestLifetimeBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 GFramework.CQRS runtime 发送 request。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 GFramework.CQRS request dispatch 完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask<BenchmarkResponse> SendRequest_GFrameworkCqrs()
|
||||
{
|
||||
@ -179,6 +185,7 @@ public class RequestLifetimeBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 MediatR 发送 request,作为外部对照。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 MediatR request dispatch 完成的任务。</returns>
|
||||
[Benchmark]
|
||||
public Task<BenchmarkResponse> SendRequest_MediatR()
|
||||
{
|
||||
@ -194,7 +201,7 @@ public class RequestLifetimeBenchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按生命周期把 benchmark request handler 注册到 GFramework 容器。
|
||||
/// 按生命周期把 benchmark request handler 注册到 GFramework 容器。
|
||||
/// </summary>
|
||||
/// <param name="container">当前 benchmark 拥有并负责释放的容器。</param>
|
||||
/// <param name="lifetime">待比较的 handler 生命周期。</param>
|
||||
@ -241,7 +248,7 @@ public class RequestLifetimeBenchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark request。
|
||||
/// Benchmark request。
|
||||
/// </summary>
|
||||
/// <param name="Id">请求标识。</param>
|
||||
public sealed record BenchmarkRequest(Guid Id) :
|
||||
|
||||
@ -113,6 +113,7 @@ public class RequestPipelineBenchmarks
|
||||
/// <summary>
|
||||
/// 直接调用 handler,作为 pipeline 编排之外的基线。
|
||||
/// </summary>
|
||||
/// <returns>代表基线 handler 完成当前 request 处理的值任务。</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ValueTask<BenchmarkResponse> SendRequest_Baseline()
|
||||
{
|
||||
@ -122,6 +123,7 @@ public class RequestPipelineBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 GFramework.CQRS runtime 发送 request,并按当前矩阵配置执行 pipeline。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 GFramework.CQRS request pipeline dispatch 完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask<BenchmarkResponse> SendRequest_GFrameworkCqrs()
|
||||
{
|
||||
@ -131,6 +133,7 @@ public class RequestPipelineBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 MediatR 发送 request,并按当前矩阵配置执行 pipeline,作为外部设计对照。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 MediatR request pipeline dispatch 完成的任务。</returns>
|
||||
[Benchmark]
|
||||
public Task<BenchmarkResponse> SendRequest_MediatR()
|
||||
{
|
||||
|
||||
@ -97,6 +97,7 @@ public class RequestStartupBenchmarks
|
||||
/// <summary>
|
||||
/// 返回已构建宿主中的 MediatR mediator,作为 initialization 组的句柄解析 baseline。
|
||||
/// </summary>
|
||||
/// <returns>当前 benchmark 复用的 MediatR mediator。</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
[BenchmarkCategory("Initialization")]
|
||||
public IMediator Initialization_MediatR()
|
||||
@ -107,6 +108,7 @@ public class RequestStartupBenchmarks
|
||||
/// <summary>
|
||||
/// 返回已构建宿主中的 GFramework.CQRS runtime,确保与 MediatR baseline 处于相同初始化阶段。
|
||||
/// </summary>
|
||||
/// <returns>当前 benchmark 复用的 GFramework.CQRS runtime。</returns>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("Initialization")]
|
||||
public ICqrsRuntime Initialization_GFrameworkCqrs()
|
||||
@ -117,6 +119,7 @@ public class RequestStartupBenchmarks
|
||||
/// <summary>
|
||||
/// 返回已构建宿主中的 `Mediator` concrete mediator,作为 source-generated 对照组的初始化句柄。
|
||||
/// </summary>
|
||||
/// <returns>当前 benchmark 复用的 `Mediator` concrete mediator。</returns>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("Initialization")]
|
||||
public GeneratedMediator Initialization_Mediator()
|
||||
@ -127,6 +130,7 @@ public class RequestStartupBenchmarks
|
||||
/// <summary>
|
||||
/// 在新宿主上首次发送 request,作为 MediatR 的 cold-start baseline。
|
||||
/// </summary>
|
||||
/// <returns>当前 request 的响应结果。</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
[BenchmarkCategory("ColdStart")]
|
||||
public async Task<BenchmarkResponse> ColdStart_MediatR()
|
||||
@ -139,6 +143,7 @@ public class RequestStartupBenchmarks
|
||||
/// <summary>
|
||||
/// 在新 runtime 上首次发送 request,量化 GFramework.CQRS 的 first-hit 成本。
|
||||
/// </summary>
|
||||
/// <returns>当前 request 的响应结果。</returns>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("ColdStart")]
|
||||
public async ValueTask<BenchmarkResponse> ColdStart_GFrameworkCqrs()
|
||||
@ -151,6 +156,7 @@ public class RequestStartupBenchmarks
|
||||
/// <summary>
|
||||
/// 在新的 `Mediator` 宿主上首次发送 request,量化 source-generated concrete path 的 cold-start 成本。
|
||||
/// </summary>
|
||||
/// <returns>当前 request 的响应结果。</returns>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("ColdStart")]
|
||||
public async ValueTask<BenchmarkResponse> ColdStart_Mediator()
|
||||
|
||||
@ -143,6 +143,7 @@ public class StreamInvokerBenchmarks
|
||||
/// <summary>
|
||||
/// 直接调用最小 stream handler,并按当前观测模式消费 stream,作为 dispatch 额外开销 baseline。
|
||||
/// </summary>
|
||||
/// <returns>代表基线 stream 按当前观测模式消费完成的值任务。</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ValueTask Stream_Baseline()
|
||||
{
|
||||
@ -152,6 +153,7 @@ public class StreamInvokerBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 GFramework.CQRS 反射 stream binding 路径创建 stream,并按当前观测模式消费。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 GFramework.CQRS 反射 stream 按观测模式消费完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask Stream_GFrameworkReflection()
|
||||
{
|
||||
@ -166,6 +168,7 @@ public class StreamInvokerBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 generated stream invoker provider 预热后的 GFramework.CQRS runtime 创建 stream,并按当前观测模式消费。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 GFramework.CQRS generated stream 按观测模式消费完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask Stream_GFrameworkGenerated()
|
||||
{
|
||||
@ -180,6 +183,7 @@ public class StreamInvokerBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 MediatR 创建 stream,并按当前观测模式消费,作为外部对照。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 MediatR stream 按观测模式消费完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask Stream_MediatR()
|
||||
{
|
||||
|
||||
@ -148,6 +148,7 @@ public class StreamLifetimeBenchmarks
|
||||
/// <summary>
|
||||
/// 直接调用 handler,并按当前观测模式消费 stream,作为不同生命周期矩阵下的 dispatch 额外开销 baseline。
|
||||
/// </summary>
|
||||
/// <returns>代表基线 handler stream 按当前观测模式消费完成的值任务。</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ValueTask Stream_Baseline()
|
||||
{
|
||||
@ -157,6 +158,7 @@ public class StreamLifetimeBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 GFramework.CQRS reflection stream binding 路径创建 stream,并按当前观测模式消费。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 reflection stream 按当前观测模式消费完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask Stream_GFrameworkReflection()
|
||||
{
|
||||
@ -183,6 +185,7 @@ public class StreamLifetimeBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 generated stream invoker provider 预热后的 GFramework.CQRS runtime 创建 stream,并按当前观测模式消费。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 generated stream 按当前观测模式消费完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask Stream_GFrameworkGenerated()
|
||||
{
|
||||
@ -209,6 +212,7 @@ public class StreamLifetimeBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 MediatR 创建 stream,并按当前观测模式消费,作为外部对照。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 MediatR stream 按当前观测模式消费完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask Stream_MediatR()
|
||||
{
|
||||
|
||||
@ -19,6 +19,7 @@ using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using GeneratedMediator = Mediator.Mediator;
|
||||
|
||||
[assembly: GFramework.Cqrs.CqrsHandlerRegistryAttribute(
|
||||
typeof(GFramework.Cqrs.Benchmarks.Messaging.StreamStartupBenchmarks.GeneratedRegistry))]
|
||||
@ -26,7 +27,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
namespace GFramework.Cqrs.Benchmarks.Messaging;
|
||||
|
||||
/// <summary>
|
||||
/// 对比 stream 宿主在 GFramework.CQRS reflection / generated 与 MediatR 之间的初始化与首次建流命中成本。
|
||||
/// 对比 stream 宿主在 GFramework.CQRS reflection / generated、NuGet `Mediator` 与 MediatR 之间的初始化与首次建流命中成本。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该场景与 <see cref="RequestStartupBenchmarks" /> 保持相同的 `Initialization + ColdStart` 结构,
|
||||
@ -45,7 +46,9 @@ public class StreamStartupBenchmarks
|
||||
private MicrosoftDiContainer _generatedContainer = null!;
|
||||
private ICqrsRuntime _generatedRuntime = null!;
|
||||
private ServiceProvider _serviceProvider = null!;
|
||||
private ServiceProvider _mediatorServiceProvider = null!;
|
||||
private IMediator _mediatr = null!;
|
||||
private GeneratedMediator _mediator = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 配置 stream startup benchmark 的公共输出格式。
|
||||
@ -67,7 +70,7 @@ public class StreamStartupBenchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建 startup benchmark 复用的 reflection / generated / MediatR 宿主对象。
|
||||
/// 构建 startup benchmark 复用的 reflection / generated / `Mediator` / MediatR 宿主对象。
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
@ -82,6 +85,8 @@ public class StreamStartupBenchmarks
|
||||
|
||||
_serviceProvider = CreateMediatRServiceProvider();
|
||||
_mediatr = _serviceProvider.GetRequiredService<IMediator>();
|
||||
_mediatorServiceProvider = CreateMediatorServiceProvider();
|
||||
_mediator = _mediatorServiceProvider.GetRequiredService<GeneratedMediator>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -99,7 +104,7 @@ public class StreamStartupBenchmarks
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
BenchmarkCleanupHelper.DisposeAll(_reflectionContainer, _generatedContainer, _serviceProvider);
|
||||
BenchmarkCleanupHelper.DisposeAll(_reflectionContainer, _generatedContainer, _serviceProvider, _mediatorServiceProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -135,6 +140,17 @@ public class StreamStartupBenchmarks
|
||||
return _generatedRuntime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回已构建宿主中的 `Mediator` concrete mediator,作为 source-generated concrete path 的初始化句柄。
|
||||
/// </summary>
|
||||
/// <returns>当前 benchmark 复用的 `Mediator` concrete mediator。</returns>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("Initialization")]
|
||||
public GeneratedMediator Initialization_Mediator()
|
||||
{
|
||||
return _mediator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在新宿主上首次创建并推进 stream,作为 MediatR 的 cold-start baseline。
|
||||
/// </summary>
|
||||
@ -180,6 +196,19 @@ public class StreamStartupBenchmarks
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在新的 `Mediator` 宿主上首次创建并推进 stream,量化 source-generated concrete path 的 first-hit 成本。
|
||||
/// </summary>
|
||||
/// <returns>首个 stream 响应元素。</returns>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("ColdStart")]
|
||||
public async ValueTask<BenchmarkResponse> ColdStart_Mediator()
|
||||
{
|
||||
using var serviceProvider = CreateMediatorServiceProvider();
|
||||
var mediator = serviceProvider.GetRequiredService<GeneratedMediator>();
|
||||
return await ConsumeFirstItemAsync(mediator.CreateStream(Request, CancellationToken.None), CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建只承载当前 benchmark handler 的最小 reflection GFramework.CQRS 容器。
|
||||
/// </summary>
|
||||
@ -229,6 +258,14 @@ public class StreamStartupBenchmarks
|
||||
ServiceLifetime.Transient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建只承载当前 benchmark handler 的最小 `Mediator` 对照宿主。
|
||||
/// </summary>
|
||||
private static ServiceProvider CreateMediatorServiceProvider()
|
||||
{
|
||||
return BenchmarkHostFactory.CreateMediatorServiceProvider(configure: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 推进 stream 到首个元素,并返回该元素作为 cold-start 结果。
|
||||
/// </summary>
|
||||
@ -274,6 +311,7 @@ public class StreamStartupBenchmarks
|
||||
/// <param name="ItemCount">返回元素数量。</param>
|
||||
public sealed record BenchmarkStreamRequest(Guid Id, int ItemCount) :
|
||||
GFramework.Cqrs.Abstractions.Cqrs.IStreamRequest<BenchmarkResponse>,
|
||||
Mediator.IStreamRequest<BenchmarkResponse>,
|
||||
MediatR.IStreamRequest<BenchmarkResponse>;
|
||||
|
||||
/// <summary>
|
||||
@ -283,10 +321,11 @@ public class StreamStartupBenchmarks
|
||||
public sealed record BenchmarkResponse(Guid Id);
|
||||
|
||||
/// <summary>
|
||||
/// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 stream handler。
|
||||
/// 同时实现 GFramework.CQRS、NuGet `Mediator` 与 MediatR 契约的最小 stream handler。
|
||||
/// </summary>
|
||||
public sealed class BenchmarkStreamHandler :
|
||||
GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler<BenchmarkStreamRequest, BenchmarkResponse>,
|
||||
Mediator.IStreamRequestHandler<BenchmarkStreamRequest, BenchmarkResponse>,
|
||||
MediatR.IStreamRequestHandler<BenchmarkStreamRequest, BenchmarkResponse>
|
||||
{
|
||||
/// <summary>
|
||||
@ -302,6 +341,19 @@ public class StreamStartupBenchmarks
|
||||
return EnumerateAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 NuGet `Mediator` stream request。
|
||||
/// </summary>
|
||||
/// <param name="request">当前 stream 请求。</param>
|
||||
/// <param name="cancellationToken">用于中断异步枚举的取消令牌。</param>
|
||||
/// <returns>按请求元素数量延迟生成的异步响应序列。</returns>
|
||||
IAsyncEnumerable<BenchmarkResponse> Mediator.IStreamRequestHandler<BenchmarkStreamRequest, BenchmarkResponse>.Handle(
|
||||
BenchmarkStreamRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Handle(request, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 MediatR stream request。
|
||||
/// </summary>
|
||||
|
||||
@ -135,6 +135,7 @@ public class StreamingBenchmarks
|
||||
/// <summary>
|
||||
/// 直接调用 handler,并按当前观测模式消费响应序列,作为 stream dispatch 额外开销的 baseline。
|
||||
/// </summary>
|
||||
/// <returns>按当前观测模式完成 stream 消费后的等待句柄。</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ValueTask Stream_Baseline()
|
||||
{
|
||||
@ -144,6 +145,7 @@ public class StreamingBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 GFramework.CQRS runtime 创建 stream,并按当前观测模式消费。
|
||||
/// </summary>
|
||||
/// <returns>按当前观测模式完成 stream 消费后的等待句柄。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask Stream_GFrameworkCqrs()
|
||||
{
|
||||
@ -158,6 +160,7 @@ public class StreamingBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 MediatR 创建 stream,并按当前观测模式消费,作为外部设计对照。
|
||||
/// </summary>
|
||||
/// <returns>按当前观测模式完成 stream 消费后的等待句柄。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask Stream_MediatR()
|
||||
{
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
- request startup
|
||||
- `Messaging/RequestStartupBenchmarks.cs`
|
||||
- `Initialization` 与 `ColdStart` 两组下,`GFramework.Cqrs`、NuGet `Mediator`、`MediatR`
|
||||
- 其中 `GFramework.Cqrs` 路径是“单 handler 最小宿主 + 手工注册”的 startup/cold-start 模型,不包含更大范围的程序集扫描或完整注册协调器接线
|
||||
- stream steady-state
|
||||
- `Messaging/StreamingBenchmarks.cs`
|
||||
- baseline、默认 generated-provider 宿主接线的 `GFramework.Cqrs` runtime、NuGet `Mediator` source-generated concrete path 与 `MediatR`
|
||||
@ -39,7 +40,7 @@
|
||||
- 同时提供 `FirstItem` 与 `DrainAll` 两种观测口径
|
||||
- stream startup
|
||||
- `Messaging/StreamStartupBenchmarks.cs`
|
||||
- `Initialization` 与 `ColdStart` 两组下,`GFramework.Cqrs` reflection、`GFramework.Cqrs` generated、`MediatR`
|
||||
- `Initialization` 与 `ColdStart` 两组下,覆盖 `MediatR`、`GFramework.Cqrs` reflection、`GFramework.Cqrs` generated、NuGet `Mediator` 四组 initialization/cold-start 对照
|
||||
- 其中 `ColdStart` 的边界是“新宿主 + 首个元素命中”,不是完整枚举整个 stream
|
||||
- notification steady-state
|
||||
- `Messaging/NotificationBenchmarks.cs`
|
||||
@ -51,6 +52,7 @@
|
||||
- notification startup
|
||||
- `Messaging/NotificationStartupBenchmarks.cs`
|
||||
- `Initialization` 与 `ColdStart` 两组下,`GFramework.Cqrs`、NuGet `Mediator`、`MediatR`
|
||||
- 其中 `GFramework.Cqrs` 路径是“单 handler 最小宿主 + 手工注册”的 startup/cold-start 模型,不包含 fan-out、发布策略变体或更大范围的注册协调逻辑
|
||||
|
||||
## 最小使用方式
|
||||
|
||||
@ -95,11 +97,12 @@ dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.cspro
|
||||
- `FirstItem` 适合观察“建流到首个元素”的固定成本
|
||||
- `DrainAll` 适合观察完整枚举整个 stream 的总成本
|
||||
- `StreamStartupBenchmarks` 的 `ColdStart` 只推进到首个元素,因此它回答的是“新宿主下首次建流命中”的边界,不回答完整枚举总成本
|
||||
- `RequestStartupBenchmarks` 与 `NotificationStartupBenchmarks` 的 `GFramework.Cqrs` startup 路径都固定在单 handler、最小宿主、手工注册模型;它们回答的是首次 request / publish 命中的额外成本,不代表程序集扫描或完整注册协调器场景
|
||||
- 当前 HEAD 没有单独固化的 short-job benchmark 类或 checked-in short-job 结果;如果手动使用 short job / short run 只做 smoke 复核,应把它理解为“确认矩阵与路径能跑通”
|
||||
- 特别是 `StreamInvokerBenchmarks` 的 `DrainAll` 在 short-job smoke 下不应直接写成 reflection、generated 或 `MediatR` 之间的稳定排序结论;若要比较名次或小幅差值,应复跑默认作业或更完整的批次
|
||||
|
||||
## 当前缺口
|
||||
|
||||
- 当前没有 stream 生命周期与 startup 版的 NuGet `Mediator` source-generated concrete path 对照;`StreamLifetimeBenchmarks` 与 `StreamStartupBenchmarks` 现在都只覆盖 `GFramework.Cqrs` 与 `MediatR`
|
||||
- 当前没有 request 生命周期下的 NuGet `Mediator` compile-time lifetime 矩阵;`RequestLifetimeBenchmarks` 只覆盖 `GFramework.Cqrs` 与 `MediatR`
|
||||
- 当前没有 stream 生命周期版的 NuGet `Mediator` source-generated concrete path 对照;`StreamLifetimeBenchmarks` 现在只覆盖 `GFramework.Cqrs` 与 `MediatR`
|
||||
- 当前没有 request 生命周期版的 NuGet `Mediator` source-generated concrete path 对照;`Mediator` 的 DI lifetime 由 source generator 在 benchmark 项目编译期固定,若要比较 `Singleton / Scoped / Transient`,需要拆成独立 build config 或独立 benchmark 工程,而不是在同一份生成产物里切换
|
||||
- 当前没有 notification fan-out 的生命周期矩阵;`NotificationFanOutBenchmarks` 只覆盖固定 `4 handler` 的已装配宿主
|
||||
|
||||
@ -12,80 +12,75 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-136`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-140`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前 PR 锚点:`PR #349`
|
||||
- 当前 PR 锚点:`PR #350(OPEN,2026-05-12)`
|
||||
- 当前结论:
|
||||
- 本轮先按 `$gframework-pr-review` 重新确认当前分支最新 GitHub 上下文,确认 `feat/cqrs-optimization` 在 `2026-05-12` 已切到 `PR #349`,不再沿用旧 tracking 中的 `PR #348` 锚点。
|
||||
- 随后按 `$gframework-batch-boot 50` 持续协调多波 non-conflicting subagent,基线固定为
|
||||
`origin/main @ ef4d3d5d (2026-05-11 17:33:43 +0800)`。
|
||||
- 当前 branch 相对基线的累计 diff 约为 `9 files / 1111 lines`;本轮停点由
|
||||
`context-budget / reviewability` 决定,而不是 `50 files` 阈值。
|
||||
- `PR #349` latest-head review 当前确认仍成立的项只有:
|
||||
- `StreamPipelineBenchmarks` 三个公开 benchmark 方法补齐 `<returns>` XML 契约
|
||||
- `StreamingBenchmarks.Stream_Mediator` 补齐 `<returns>` XML 契约
|
||||
- `CqrsNotificationPublisherTests` 中 fallback publisher 缓存回归测试去掉误导性的“第二次解析返回其它 publisher”分支
|
||||
- active tracking / trace 的当前 PR 锚点与下一步入口同步到 `PR #349`
|
||||
- tests 侧已补齐并提交:
|
||||
- `CqrsRegistrationServiceTests`:补空输入、空项过滤、稳定键排序与跨调用跳过边界
|
||||
- `CqrsHandlerRegistrarTests` 与 `CqrsHandlerRegistrarFallbackFailureTests`:
|
||||
补 abstract registry 与缺少无参构造器 registry 的回退 / 抛错覆盖
|
||||
- `CqrsNotificationPublisherTests`:补“零 publisher 回退到默认顺序发布器并缓存”回归
|
||||
- benchmark 侧已补齐并提交:
|
||||
- `StreamPipelineBenchmarks`
|
||||
- `StreamingBenchmarks` 的 steady-state `Mediator` 对照
|
||||
- `GFramework.Cqrs.Benchmarks/README.md` 的 stream coverage / gap 同步
|
||||
- 本轮未修改 `GFramework.Cqrs` 运行时代码;notification fallback 与 generated registry 激活守卫均由新回归证明现有实现已满足预期。
|
||||
- 本轮按 `$gframework-pr-review` 重新抓取 GitHub 真值后,确认当前公开 PR 不是已合并的 `PR #349`,而是仍处于 `OPEN` 状态的 `PR #350`。
|
||||
- 最新 AI review 只有 1 条 Greptile open thread,关注点是:
|
||||
- `StreamStartupBenchmarks.ColdStart_Mediator()` 与 `RequestLifetimeBenchmarks.SendRequest_Mediator()` 先前只做了编译验证,未实际 smoke-run
|
||||
- 主线程按 review 提示执行最小 benchmark smoke run 后,确认 Greptile 线程不是误报,而是命中了真实运行时缺陷:
|
||||
- `StreamStartupBenchmarks.ColdStart_Mediator()` 在 BenchmarkDotNet 自动生成宿主里抛出
|
||||
`Invalid configuration detected for Mediator. Generated code for 'Transient' lifetime, but got 'Singleton' lifetime from options.`
|
||||
- `RequestLifetimeBenchmarks.SendRequest_Mediator()` 的 `Singleton / Scoped` 也抛出同类异常;只有 `Transient` 变体能跑通
|
||||
- 根因确认:
|
||||
- NuGet `Mediator` 的 DI lifetime 由 source generator 在 benchmark 项目编译期固定
|
||||
- 当前工程同时存在默认 `AddMediator()` 与 request lifetime 场景下的 `AddMediator(options => options.ServiceLifetime = ...)`
|
||||
- 这会让同一份生成产物在 BenchmarkDotNet 自动生成宿主中出现 compile-time 形状与 runtime options 不一致
|
||||
- 本轮收口策略:
|
||||
- `BenchmarkHostFactory.CreateMediatorServiceProvider()` 统一显式固定为 `Singleton` compile-time lifetime
|
||||
- `RequestLifetimeBenchmarks` 撤回当前无法真实运行的 `Mediator` 生命周期矩阵,只保留 `GFramework.Cqrs` 与 `MediatR`
|
||||
- `GFramework.Cqrs.Benchmarks/README.md` 同步收窄 request lifetime coverage,并把 `Mediator` 生命周期矩阵改记为当前缺口
|
||||
- 本轮未修改 `GFramework.Cqrs` 运行时代码;修复面限定在 benchmark 宿主装配与 reader-facing docs。
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
- 当前分支:`feat/cqrs-optimization`
|
||||
- 当前 PR:`PR #349`
|
||||
- 当前 PR:`PR #349(已合并;当前分支暂无新的公开 PR)`
|
||||
- 当前 PR:`PR #350(OPEN)`
|
||||
- 当前写面:
|
||||
- `GFramework.Cqrs.Benchmarks/Messaging/StreamPipelineBenchmarks.cs`
|
||||
- `GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs`
|
||||
- `GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs`
|
||||
- `GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs`
|
||||
- `GFramework.Cqrs.Benchmarks/README.md`
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarFallbackFailureTests.cs`
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs`
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsNotificationPublisherTests.cs`
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsRegistrationServiceTests.cs`
|
||||
- `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md`
|
||||
- `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md`
|
||||
- 当前基线:
|
||||
- `origin/main @ ef4d3d5d (2026-05-11 17:33:43 +0800)`
|
||||
- 本轮 batch 启动前,分支相对基线的累计 diff 为 `0 files / 0 lines`
|
||||
- 当前自然停点时,累计 diff 约为 `9 files / 1111 lines`
|
||||
- `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`
|
||||
- 当前已提交 branch diff:`14 files`
|
||||
- 当前分支比 `origin/main` 多 `5` 个提交:`f346110a`、`a016e3d4`、`ab422b05`、`555c7c07`、`c32a1ec4`
|
||||
- 当前工作面已收口为 `Mediator` benchmark runtime 配置修正、request lifetime coverage 收窄与对应 `README` / `ai-plan` 同步
|
||||
- 本轮提交:
|
||||
- `ef3cfdc4` `test(cqrs): 补充注册服务边界测试`
|
||||
- `bcfecd3c` `test(cqrs): 补充 registrar 激活失败分支测试`
|
||||
- `59cab567` `test(cqrs-benchmarks): 新增 stream pipeline benchmark 覆盖`
|
||||
- `010b7028` `test(cqrs): 补充通知回退回归覆盖`
|
||||
- `ae1c3b89` `test(cqrs-benchmarks): 补齐 stream steady-state Mediator 对照`
|
||||
- `f346110a` `feat(cqrs-benchmarks): 补齐 stream startup 的 Mediator 对照路径`
|
||||
- `ab422b05` `docs(cqrs-benchmarks): 补齐 request benchmark 返回值注释`
|
||||
- `555c7c07` `docs(cqrs-benchmarks): 补齐 request benchmark 返回值文档`
|
||||
- `c32a1ec4` `docs(cqrs-benchmarks): 补齐stream与notification基准返回值文档`
|
||||
|
||||
## 当前风险
|
||||
|
||||
- 分支已累积 5 个窄切片提交;若继续在同一 turn 扩 benchmark + docs,reviewability 会明显下降。
|
||||
- 新增 benchmark 目前只做了编译验证,尚未执行 `StreamPipelineBenchmarks` 或更新后的 `StreamingBenchmarks` 实际作业。
|
||||
- `ef3cfdc4` 的 commit body 含字面 `\n`;若后续要整理历史,需要在显式允许的前提下单独处理提交格式。
|
||||
- `StreamLifetimeBenchmarks` 仍缺 `Mediator` parity;如果后续要补,必须采用独立 compile-time config 或独立 benchmark 工程,而不是在当前项目里切换 runtime `ServiceLifetime`。
|
||||
- `RequestLifetimeBenchmarks` 目前不再覆盖 `Mediator`;若后续要恢复该矩阵,也必须先解决 source-generated lifetime 与 BenchmarkDotNet 自动宿主的编译期塑形边界。
|
||||
- benchmark XML 盘点若再次依赖粗糙脚本或只读 inventory,仍有把已存在文档误记为缺口的风险;后续若再开 XML 波次,必须先用主线程抽样核对代表文件。
|
||||
- 当前 PR 的 Greptile open thread 在代码修正后虽已有本地验证证据,但线程本身还未在 GitHub 上回复 / resolve。
|
||||
|
||||
## 最近权威验证
|
||||
|
||||
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsNotificationPublisherTests"`
|
||||
- 结果:通过,`Passed: 9, Failed: 0`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsRegistrationServiceTests|FullyQualifiedName~CqrsHandlerRegistrarTests|FullyQualifiedName~CqrsHandlerRegistrarFallbackFailureTests|FullyQualifiedName~CqrsNotificationPublisherTests"`
|
||||
- 结果:通过,`Passed: 36, Failed: 0`
|
||||
- `python3 scripts/license-header.py --check --paths GFramework.Cqrs.Benchmarks/Messaging/StreamPipelineBenchmarks.cs GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs GFramework.Cqrs.Benchmarks/README.md GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarFallbackFailureTests.cs GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs GFramework.Cqrs.Tests/Cqrs/CqrsNotificationPublisherTests.cs GFramework.Cqrs.Tests/Cqrs/CqrsRegistrationServiceTests.cs ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md`
|
||||
- 备注:确认统一 `Mediator` compile-time lifetime 后 benchmark 工程仍可编译
|
||||
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --artifacts-suffix pr350-stream-startup-mediator-fixed --filter "*StreamStartupBenchmarks.ColdStart_Mediator*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- `git diff --check origin/main...HEAD`
|
||||
- 备注:`ColdStart_Mediator` 已在 BenchmarkDotNet 自动生成宿主中实际执行,约 `144.036 us / 69.3 KB`
|
||||
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --artifacts-suffix pr350-request-lifetime-fixed-rerun --filter "*RequestLifetimeBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 备注:当前矩阵为 `9` 项(baseline / `GFramework.Cqrs` / `MediatR` * `Singleton|Scoped|Transient`),不再包含伪 `Mediator` lifetime 条目
|
||||
- `$gframework-pr-review`
|
||||
- 结果:确认 `PR #350` open,CodeRabbit 已 `APPROVED`,Greptile 仍有 `1` 条 open thread 指向 `StreamStartupBenchmarks.cs`
|
||||
|
||||
## 下一推荐步骤
|
||||
|
||||
1. 再次运行 `$gframework-pr-review`,复核 `PR #349` latest-head open thread 是否已随着当前修复提交收敛。
|
||||
2. 若继续扩 benchmark,优先在 `StreamLifetimeBenchmarks` 或 `StreamStartupBenchmarks` 中补单文件 `Mediator` parity,而不是并行扩多个矩阵。
|
||||
3. 若切回文档收尾,把 `GFramework.Cqrs/README.md`、`docs/zh-CN/core/command.md`、`docs/zh-CN/core/query.md` 作为单独一波 docs-only 切片处理。
|
||||
1. 在 GitHub `PR #350` 回应并 resolve 当前 Greptile 线程,说明 `ColdStart_Mediator` 已补 smoke-run,且 request lifetime 的 `Mediator` 矩阵已按 source-generator 真实约束撤回。
|
||||
2. 若后续评估 `StreamLifetimeBenchmarks` 或 request lifetime 的 `Mediator` parity,优先设计独立 compile-time config / 独立 benchmark 工程,而不是继续在同一项目里切换 runtime `ServiceLifetime`。
|
||||
3. 若后续再开 XML / docs 批次,先由主线程逐文件核对代表样本,不要直接沿用误报 inventory 扩批。
|
||||
|
||||
## 活跃文档
|
||||
|
||||
|
||||
@ -7,6 +7,210 @@ SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
## 2026-05-12
|
||||
|
||||
### 阶段:PR #350 的 Mediator runtime 配置收口(CQRS-REWRITE-RP-141)
|
||||
|
||||
- 使用 `$gframework-pr-review` 重新抓取当前分支 PR,确认 GitHub 真值已从 active tracking 中过期的 `PR #349` 切换为仍处于 `OPEN` 状态的 `PR #350`。
|
||||
- 最新 AI review 只剩 1 条 Greptile open thread:
|
||||
- `GFramework.Cqrs.Benchmarks/Messaging/StreamStartupBenchmarks.cs:210`
|
||||
- 质疑点不是文档,而是 `Mediator` startup / request lifetime 新路径仅编译通过、未实际 smoke-run
|
||||
- 主线程先按 review 建议做本地 smoke 验证,而不是直接回复线程:
|
||||
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --artifacts-suffix pr350-stream-startup-mediator --filter "*StreamStartupBenchmarks.ColdStart_Mediator*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --artifacts-suffix pr350-request-lifetime-mediator --filter "*RequestLifetimeBenchmarks.SendRequest_Mediator*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 第一轮 smoke 暴露出 Greptile 线程背后的真实运行时问题,而不是 review 噪音:
|
||||
- `StreamStartupBenchmarks.ColdStart_Mediator()` 在 BenchmarkDotNet 自动生成宿主里抛出
|
||||
`Invalid configuration detected for Mediator. Generated code for 'Transient' lifetime, but got 'Singleton' lifetime from options.`
|
||||
- `RequestLifetimeBenchmarks.SendRequest_Mediator()` 的 `Singleton` / `Scoped` 同样抛出相同异常,只有 `Transient` 分支能实际执行
|
||||
- 根因判断:
|
||||
- `Mediator` 的 DI lifetime 由 source generator 在 benchmark 项目编译期固定
|
||||
- 当前项目同时包含默认 `AddMediator()` 和 request lifetime 场景里的 `AddMediator(options => options.ServiceLifetime = ...)`
|
||||
- 同一份生成产物在 BenchmarkDotNet 自动生成宿主里因此出现 compile-time lifetime 与 runtime options 不一致
|
||||
- 主线程修复:
|
||||
- `BenchmarkHostFactory.CreateMediatorServiceProvider()` 统一改为显式 `ServiceLifetime.Singleton`
|
||||
- `RequestLifetimeBenchmarks` 删除当前无法真实运行的 `SendRequest_Mediator()` 与相关 `Mediator` 生命周期 helper / 契约实现
|
||||
- `GFramework.Cqrs.Benchmarks/README.md` 将 request lifetime coverage 收窄为 `GFramework.Cqrs` + `MediatR`,并把 `Mediator` lifetime parity 改记为当前缺口
|
||||
- 串行验证结果:
|
||||
- `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 -- --artifacts-suffix pr350-stream-startup-mediator-fixed --filter "*StreamStartupBenchmarks.ColdStart_Mediator*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 关键数值:`ColdStart_Mediator ≈ 144.036 us / 69.3 KB`
|
||||
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --artifacts-suffix pr350-request-lifetime-fixed-rerun --filter "*RequestLifetimeBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 关键结论:当前 request lifetime 矩阵已收敛为 `9` 项(baseline / `GFramework.Cqrs` / `MediatR` * `Singleton|Scoped|Transient`),不再包含伪 `Mediator` lifetime 条目
|
||||
- 本轮 stop decision:
|
||||
- 不继续把 `Mediator` lifetime parity 硬扩到 request 或 stream lifetime benchmark
|
||||
- 原因不是 branch-size;而是 source-generator compile-time config 已明确构成真实边界,继续在同一项目里扩 runtime 切换只会制造新的伪覆盖
|
||||
|
||||
### 阶段:request lifetime 的 Mediator parity 与文档漂移收口(CQRS-REWRITE-RP-140)
|
||||
|
||||
- 继续按 `$gframework-batch-boot 50` 推进,基线保持为 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`。
|
||||
- 本轮启动时重新测得当前已提交 branch diff 仍为 `14 files / 324 lines`,远低于 `50 files` 阈值;停止与否继续由 context-budget / reviewability 主导。
|
||||
- 主线程结合两个 explorer 子代理的只读盘点后,接受以下结论:
|
||||
- 不再继续按 benchmark XML `<returns>` inventory 机械扩批;粗糙脚本会把“注释位于 `[Benchmark]` 之前”的现有文档误判为缺口
|
||||
- `RequestLifetimeBenchmarks` 的 NuGet `Mediator` lifetime parity 是当前仍然真实、且能保持 reviewable 的实现候选
|
||||
- `NotificationBenchmarks.cs` 与 `RequestBenchmarks.cs` 仍有两处低风险 XML 文档漂移,均只涉及 NuGet `Mediator` 事实同步
|
||||
- 本轮主线程实施:
|
||||
- `RequestLifetimeBenchmarks.cs`
|
||||
- 新增 `GeneratedMediator` 宿主字段、`SendRequest_Mediator()` benchmark 方法与 scoped `Mediator` request helper
|
||||
- 将 `BenchmarkRequest` / `BenchmarkRequestHandler` 扩为同时实现 `Mediator` 契约
|
||||
- 为 `Mediator` 宿主改用 `Singleton / Scoped / Transient` 三个编译期常量分支,规避 `MSG0007` 对运行时 lifetime 赋值的生成器限制
|
||||
- `RequestBenchmarks.cs`
|
||||
- 将 handler XML 文档说明补齐为同时实现 `GFramework.CQRS`、NuGet `Mediator` 与 `MediatR`
|
||||
- `NotificationBenchmarks.cs`
|
||||
- 将类说明与 handler XML 文档说明补齐为同时覆盖 `GFramework.CQRS`、NuGet `Mediator` 与 `MediatR`
|
||||
- `GFramework.Cqrs.Benchmarks/README.md`
|
||||
- 将 `RequestLifetimeBenchmarks` 的 coverage 更新为包含 NuGet `Mediator` source-generated concrete path
|
||||
- 删除“当前没有 request 生命周期下的 NuGet `Mediator` compile-time lifetime 矩阵”这一已过时缺口
|
||||
- 验证里程碑:
|
||||
- 第一次 `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||
- 结果:失败
|
||||
- 原因:`RequestLifetimeBenchmarks.cs` 中基于运行时变量写入 `MediatorOptions.ServiceLifetime`,触发 `MSG0007`
|
||||
- 主线程修正:
|
||||
- 将 `CreateMediatorServiceProvider(HandlerLifetime lifetime)` 收口为 3 个常量分支工厂:
|
||||
`CreateSingletonMediatorServiceProvider()`、`CreateScopedMediatorServiceProvider()`、`CreateTransientMediatorServiceProvider()`
|
||||
- 第二次 `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- 当前 stop decision:
|
||||
- 不继续开启新的实现波次
|
||||
- 原因不是 branch-size 阈值耗尽;当前分支仍只有 `14 files`
|
||||
- 停止原因是本轮已经完成一条真实 parity 收口和两处文档漂移修正,继续扩到 `StreamLifetimeBenchmarks` 会显著提高作用域与 review 成本
|
||||
- 当前下一步:
|
||||
- 主线程补跑 `python3 scripts/license-header.py --check --paths ...` 与 `git diff --check`
|
||||
- 更新 active tracking / trace 后提交当前 benchmark 代码、README 与 `ai-plan`
|
||||
|
||||
### 阶段:README startup coverage 精度同步并停在自然边界(CQRS-REWRITE-RP-139)
|
||||
|
||||
- 继续按 `$gframework-batch-boot 50` 推进,基线保持为 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`。
|
||||
- 本轮启动时重新测得当前已提交 branch diff 为 `14 files`,仍远低于 `50 files` 阈值;继续与否的主停止信号仍是
|
||||
context-budget / reviewability。
|
||||
- 本轮主线程先做只读盘点与抽样核对:
|
||||
- README 一致性 explorer 结论成立:`GFramework.Cqrs.Benchmarks/README.md` 对 startup coverage / 边界的表述仍可更精确
|
||||
- benchmark XML 缺口 explorer 结论未直接接受;主线程抽样检查 `NotificationBenchmarks.cs`、
|
||||
`RequestBenchmarks.cs`、`StreamingBenchmarks.cs`、`NotificationStartupBenchmarks.cs` 后确认,
|
||||
其 class / benchmark 方法的 `<summary>` 与 `<returns>` 实际已存在,不能继续按“14 个门面文件普遍缺 XML”
|
||||
的假设扩批
|
||||
- 因此本轮 accepted delegated scope 缩成单文件 docs-only worker:
|
||||
- `GFramework.Cqrs.Benchmarks/README.md`
|
||||
- 把 `StreamStartupBenchmarks` 明确写成 `MediatR`、`GFramework.Cqrs` reflection、
|
||||
`GFramework.Cqrs` generated、NuGet `Mediator` 四组 initialization / cold-start 对照
|
||||
- 补充 `RequestStartupBenchmarks` 与 `NotificationStartupBenchmarks` 的 `GFramework.Cqrs` startup 路径是
|
||||
“单 handler 最小宿主 + 手工注册”模型,不外推到程序集扫描、完整注册协调器、fan-out 或发布策略变体
|
||||
- worker 回传验收结论:
|
||||
- 改动文件未越出 ownership 边界
|
||||
- README diff 与代码事实一致,且未引入无法从当前 benchmark 实现验证的表述
|
||||
- 当前 stop decision:
|
||||
- 不继续开启新的 XML 文档波次
|
||||
- 原因不是 branch-size 阈值耗尽;当前分支仍只有 `14 files`
|
||||
- 停止原因是候选清晰度下降:继续追逐 explorer 误报会降低 reviewability,并无谓增加当前上下文负担
|
||||
- 当前下一步:
|
||||
- 主线程更新 `ai-plan/public/cqrs-rewrite/**`
|
||||
- 串行运行 benchmark 工程 build、license-header 与 `git diff --check`
|
||||
- 提交 README 与 `ai-plan` 收尾
|
||||
|
||||
### 阶段:benchmark XML 契约第 2 波收口(CQRS-REWRITE-RP-138)
|
||||
|
||||
- 延续 `$gframework-batch-boot 50`,基线保持为 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`。
|
||||
- 第 2 波启动前,当前分支相对基线的已提交 branch diff 为 `5 files / 177 lines`,明显低于 `50 files` 阈值;本轮继续与否的主停止信号仍是 context-budget / reviewability。
|
||||
- 主线程本地盘点 `GFramework.Cqrs.Benchmarks/Messaging/*.cs` 的公开 `[Benchmark]` 方法后,确认当前仍有一批与既有收口模式一致的 XML `<returns>` 缺口:
|
||||
- `StreamingBenchmarks.Stream_GFrameworkCqrs()`
|
||||
- `NotificationBenchmarks` 的 3 个公开 benchmark 方法
|
||||
- `NotificationFanOutBenchmarks` 的 5 个公开 benchmark 方法
|
||||
- `StreamInvokerBenchmarks` 的 4 个公开 benchmark 方法
|
||||
- `StreamLifetimeBenchmarks` 的 4 个公开 benchmark 方法
|
||||
- 本波 accepted ownership:
|
||||
- 主线程
|
||||
- `GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs`
|
||||
- `GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs`
|
||||
- `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md`
|
||||
- `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md`
|
||||
- worker
|
||||
- `StreamLifetimeBenchmarks.cs`
|
||||
- `StreamInvokerBenchmarks.cs`
|
||||
- `NotificationFanOutBenchmarks.cs`
|
||||
- worker 回传验收结论:
|
||||
- `StreamLifetimeBenchmarks.cs`
|
||||
- 只补 `Stream_Baseline`、`Stream_GFrameworkReflection`、`Stream_GFrameworkGenerated`、`Stream_MediatR` 的 `<returns>`
|
||||
- worker 自报 `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` 通过
|
||||
- `StreamInvokerBenchmarks.cs`
|
||||
- 只补 4 个公开 benchmark 方法的 `<returns>`
|
||||
- worker 自报同一条 benchmark 工程 build 通过
|
||||
- `NotificationFanOutBenchmarks.cs`
|
||||
- 只补 5 个公开 benchmark 方法的 `<returns>`
|
||||
- worker 自报 build 遇到 `CS2012`:`obj/Release/net10.0/GFramework.Cqrs.Benchmarks.dll` 被并发进程占用;该失败被判定为并发构建噪音,而不是代码语义问题
|
||||
- 主线程局部实施:
|
||||
- `StreamingBenchmarks.cs`
|
||||
- 为 `Stream_GFrameworkCqrs()` 补 `<returns>`
|
||||
- `NotificationBenchmarks.cs`
|
||||
- 为 `PublishNotification_GFrameworkCqrs()`、`PublishNotification_MediatR()`、`PublishNotification_Mediator()` 补 `<returns>`
|
||||
- 当前下一步:
|
||||
- 主线程串行执行 benchmark 工程 Release build,消除 worker 并发写 `obj/Release` 带来的验证噪音
|
||||
- 若串行验证通过,决定是在当前自然停点提交收尾,还是继续 request 侧 XML 契约的下一波低风险批处理
|
||||
|
||||
### 阶段:request benchmark XML 契约第 3 波收口后停在自然边界(CQRS-REWRITE-RP-138)
|
||||
|
||||
- 第 2 波串行验证通过后,继续用 3 个 worker 扩展 request 系 benchmark 的同类 `<returns>` 收口:
|
||||
- `RequestStartupBenchmarks.cs`
|
||||
- `RequestBenchmarks.cs` + `RequestPipelineBenchmarks.cs`
|
||||
- `RequestInvokerBenchmarks.cs` + `RequestLifetimeBenchmarks.cs`
|
||||
- worker 回传与 acceptance:
|
||||
- `RequestStartupBenchmarks.cs`
|
||||
- 只补公开 benchmark 方法缺失的 `<returns>`
|
||||
- worker 自报 `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` 通过
|
||||
- `RequestBenchmarks.cs` + `RequestPipelineBenchmarks.cs`
|
||||
- 只补公开 benchmark 方法缺失的 `<returns>`
|
||||
- worker 自报 build 通过,并已提交:`555c7c07 docs(cqrs-benchmarks): 补齐 request benchmark 返回值文档`
|
||||
- `RequestInvokerBenchmarks.cs` + `RequestLifetimeBenchmarks.cs`
|
||||
- 只补公开 benchmark 方法缺失的 `<returns>`
|
||||
- worker 自报 build 通过,并已提交:`ab422b05 docs(cqrs-benchmarks): 补齐 request benchmark 返回值注释`
|
||||
- 第 3 波后主线程 stop decision:
|
||||
- 不再开启第 4 波 XML 契约批处理
|
||||
- 原因不是 branch-size 阈值耗尽;当前分支相对 `origin/main` 仍只有 `9 files / 143 lines`
|
||||
- 停止原因是 context-budget / reviewability:剩余候选已不比当前波次更低风险,继续机械扩批收益下降
|
||||
- 当前下一步:
|
||||
- 只做主线程未提交面的串行验证与收尾提交
|
||||
- 将干净工作树作为下一次 `boot` 的默认恢复目标
|
||||
|
||||
### 阶段:stream startup parity 与文档收尾(CQRS-REWRITE-RP-137)
|
||||
|
||||
- 按 `$gframework-batch-boot 50` 恢复后,先重新执行 `$gframework-pr-review`。
|
||||
- 当前 GitHub 事实:
|
||||
- `PR #349` 已关闭并合并到 `origin/main`
|
||||
- 基线切换为 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`
|
||||
- 当前分支相对新基线的已提交 diff 初始为 `0 files / 0 lines`
|
||||
- latest-head open thread 本地复核:
|
||||
- stale
|
||||
- `StreamPipelineBenchmarks.Stream_Baseline` 的 `<returns>` 已存在
|
||||
- `CqrsNotificationPublisherTests` 的 fallback 缓存安全网已收口
|
||||
- trace 的当前 PR / 下一步已同步到 `PR #349`
|
||||
- valid
|
||||
- `StreamingBenchmarks.Stream_MediatR()` 仍缺 `<returns>` XML 文档
|
||||
- 第 1 波 accepted delegated scope:
|
||||
- `StreamingBenchmarks.cs`
|
||||
- worker 补 `Stream_Baseline()` 与 `Stream_MediatR()` 的 `<returns>` XML 契约
|
||||
- 主线程验收时确认其中 `Stream_Baseline()` 属于额外收口,不是 latest-head 必修项
|
||||
- `StreamStartupBenchmarks.cs`
|
||||
- worker 在单文件 ownership 内补 `GeneratedMediator` 宿主字段、setup/cleanup、`Initialization_Mediator()`、`ColdStart_Mediator()`
|
||||
- 同文件把 `BenchmarkStreamRequest` / `BenchmarkStreamHandler` 扩成同时支持 `Mediator` stream 合同
|
||||
- worker 自主完成并提交:`f346110a feat(cqrs-benchmarks): 补齐 stream startup 的 Mediator 对照路径`
|
||||
- `GFramework.Cqrs.Benchmarks/README.md`
|
||||
- worker 只收口 `StreamStartupBenchmarks` coverage 与当前 gap 描述,不假设 `StreamLifetimeBenchmarks` 已补 parity
|
||||
- 主线程验收结论:
|
||||
- `StreamLifetimeBenchmarks` 的 `Mediator` parity 被判定为 hard slice,需要 `BenchmarkHostFactory` 与 compile-time lifetime 配套,不再继续作为本 turn 的低风险并行切片
|
||||
- 当前自然停点应落在:
|
||||
- 已提交的 `StreamStartupBenchmarks` parity
|
||||
- 未提交但已验收的 `StreamingBenchmarks.cs` / `README.md` 收尾
|
||||
- `ai-plan` 同步到新基线与新恢复点
|
||||
- 本轮权威验证里程碑:
|
||||
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `python3 scripts/license-header.py --check`
|
||||
- 结果:通过
|
||||
- `git diff --check`
|
||||
- 待当前未提交收尾切片与 `ai-plan` 一并提交前再次运行
|
||||
- 当前下一步:
|
||||
- 提交 `StreamingBenchmarks.cs`、`GFramework.Cqrs.Benchmarks/README.md` 与 `ai-plan/public/cqrs-rewrite/**` 收尾
|
||||
- 如需继续 benchmark 波次,优先做 `StreamStartupBenchmarks` 的最小 smoke run,而不是直接展开 `StreamLifetimeBenchmarks`
|
||||
|
||||
### 阶段:PR #349 latest-head review 收口(CQRS-REWRITE-RP-136)
|
||||
|
||||
- 重新执行 `$gframework-pr-review`,按 GitHub 当前分支状态确认 `feat/cqrs-optimization` 在 `2026-05-12` 对应的是 `PR #349`,不再沿用 active tracking 中的 `PR #348` 锚点。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user