test(cqrs): 补充基准与生成器回归基础设施

- 新增独立的 GFramework.Cqrs.Benchmarks 项目并引入 request、notification 对比场景

- 补充 request 与 stream invoker provider 的 mixed direct/reflected 顺序回归测试

- 更新 solution、meta-package 排除规则与 CQRS ai-plan 恢复点
This commit is contained in:
gewuyou 2026-05-06 08:57:59 +08:00
parent a8c6c11e9e
commit 96729ddcf1
13 changed files with 912 additions and 3 deletions

View File

@ -0,0 +1,71 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using System;
namespace GFramework.Cqrs.Benchmarks;
/// <summary>
/// 为 CQRS benchmark 结果补充可读的场景标签列。
/// </summary>
/// <param name="columnName">列名。</param>
/// <param name="getValue">从 benchmark case 提取列值的委托。</param>
public sealed class CustomColumn(string columnName, Func<Summary, BenchmarkCase, string> getValue) : IColumn
{
/// <inheritdoc />
public string Id => $"{nameof(CustomColumn)}.{ColumnName}";
/// <inheritdoc />
public string ColumnName { get; } = columnName;
/// <inheritdoc />
public bool AlwaysShow => true;
/// <inheritdoc />
public ColumnCategory Category => ColumnCategory.Params;
/// <inheritdoc />
public int PriorityInCategory => 0;
/// <inheritdoc />
public bool IsNumeric => false;
/// <inheritdoc />
public UnitType UnitType => UnitType.Dimensionless;
/// <inheritdoc />
public string Legend => $"Custom '{ColumnName}' tag column";
/// <inheritdoc />
public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase)
{
return false;
}
/// <inheritdoc />
public bool IsAvailable(Summary summary)
{
return true;
}
/// <inheritdoc />
public string GetValue(Summary summary, BenchmarkCase benchmarkCase)
{
return getValue(summary, benchmarkCase);
}
/// <inheritdoc />
public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style)
{
return GetValue(summary, benchmarkCase);
}
/// <inheritdoc />
public override string ToString()
{
return ColumnName;
}
}

View File

@ -0,0 +1,29 @@
<!--
Copyright (c) 2025-2026 GeWuYou
SPDX-License-Identifier: Apache-2.0
-->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.6" />
<PackageReference Include="MediatR" Version="13.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GFramework.Cqrs.Abstractions\GFramework.Cqrs.Abstractions.csproj" />
<ProjectReference Include="..\GFramework.Cqrs\GFramework.Cqrs.csproj" />
<ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj" />
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Benchmarks.Messaging;
/// <summary>
/// 为纯 runtime benchmark 提供最小 CQRS 上下文标记,避免把完整架构上下文初始化成本混入 steady-state dispatch。
/// </summary>
internal sealed class BenchmarkContext : ICqrsContext
{
/// <summary>
/// 共享的最小 CQRS 上下文实例。
/// </summary>
public static BenchmarkContext Instance { get; } = new();
}

View File

@ -0,0 +1,35 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using BenchmarkDotNet.Loggers;
using System;
namespace GFramework.Cqrs.Benchmarks.Messaging;
/// <summary>
/// 为 CQRS benchmark 运行打印并验证当前场景配置,避免矩阵配置与实际运行环境漂移。
/// </summary>
internal static class Fixture
{
/// <summary>
/// 输出当前 benchmark 配置并验证关键环境变量。
/// </summary>
/// <param name="scenario">当前 benchmark 场景名称。</param>
/// <param name="handlerCount">当前场景的处理器数量。</param>
/// <param name="pipelineCount">当前场景的 pipeline 行为数量。</param>
public static void Setup(string scenario, int handlerCount, int pipelineCount)
{
ConsoleLogger.Default.WriteLineHeader("GFramework.Cqrs benchmark config");
ConsoleLogger.Default.WriteLineInfo($"Scenario = {scenario}");
ConsoleLogger.Default.WriteLineInfo($"HandlerCount = {handlerCount}");
ConsoleLogger.Default.WriteLineInfo($"PipelineCount = {pipelineCount}");
var environmentScenario = Environment.GetEnvironmentVariable("GFRAMEWORK_CQRS_BENCHMARK_SCENARIO");
if (!string.IsNullOrWhiteSpace(environmentScenario) &&
!string.Equals(environmentScenario, scenario, StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"Scenario mismatch. Expected '{environmentScenario}', actual '{scenario}'.");
}
}
}

View File

@ -0,0 +1,136 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Order;
using System;
using System.Threading;
using System.Threading.Tasks;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Cqrs.Abstractions.Cqrs;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace GFramework.Cqrs.Benchmarks.Messaging;
/// <summary>
/// 对比单处理器 notification 在 GFramework.CQRS 与 MediatR 之间的 publish 开销。
/// </summary>
[Config(typeof(Config))]
public class NotificationBenchmarks
{
private MicrosoftDiContainer _container = null!;
private ICqrsRuntime _runtime = null!;
private ServiceProvider _serviceProvider = null!;
private IPublisher _publisher = null!;
private BenchmarkNotification _notification = null!;
/// <summary>
/// 配置 notification benchmark 的公共输出格式。
/// </summary>
private sealed class Config : ManualConfig
{
public Config()
{
AddJob(Job.Default);
AddColumnProvider(DefaultColumnProviders.Instance);
AddColumn(new CustomColumn("Scenario", static (_, _) => "Notification"));
AddDiagnoser(MemoryDiagnoser.Default);
WithOrderer(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest, MethodOrderPolicy.Declared));
}
}
/// <summary>
/// 构建 notification publish 所需的最小 runtime 宿主和对照对象。
/// </summary>
[GlobalSetup]
public void Setup()
{
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider
{
MinLevel = LogLevel.Fatal
};
Fixture.Setup("Notification", handlerCount: 1, pipelineCount: 0);
_container = new MicrosoftDiContainer();
_container.RegisterTransient<GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler<BenchmarkNotification>, BenchmarkNotificationHandler>();
_runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
_container,
LoggerFactoryResolver.Provider.CreateLogger(nameof(NotificationBenchmarks)));
var services = new ServiceCollection();
services.AddSingleton<MediatR.INotificationHandler<BenchmarkNotification>, BenchmarkNotificationHandler>();
services.AddMediatR(static options => options.RegisterServicesFromAssembly(typeof(NotificationBenchmarks).Assembly));
_serviceProvider = services.BuildServiceProvider();
_publisher = _serviceProvider.GetRequiredService<IPublisher>();
_notification = new BenchmarkNotification(Guid.NewGuid());
}
/// <summary>
/// 释放 MediatR 对照组使用的 DI 宿主。
/// </summary>
[GlobalCleanup]
public void Cleanup()
{
_serviceProvider.Dispose();
}
/// <summary>
/// 通过 GFramework.CQRS runtime 发布 notification。
/// </summary>
[Benchmark(Baseline = true)]
public ValueTask PublishNotification_GFrameworkCqrs()
{
return _runtime.PublishAsync(BenchmarkContext.Instance, _notification, CancellationToken.None);
}
/// <summary>
/// 通过 MediatR 发布 notification作为外部设计对照。
/// </summary>
[Benchmark]
public Task PublishNotification_MediatR()
{
return _publisher.Publish(_notification, CancellationToken.None);
}
/// <summary>
/// Benchmark notification。
/// </summary>
/// <param name="Id">通知标识。</param>
public sealed record BenchmarkNotification(Guid Id) :
GFramework.Cqrs.Abstractions.Cqrs.INotification,
MediatR.INotification;
/// <summary>
/// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 notification handler。
/// </summary>
public sealed class BenchmarkNotificationHandler :
GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler<BenchmarkNotification>,
MediatR.INotificationHandler<BenchmarkNotification>
{
/// <summary>
/// 处理 GFramework.CQRS notification。
/// </summary>
public ValueTask Handle(BenchmarkNotification notification, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
/// <summary>
/// 处理 MediatR notification。
/// </summary>
Task MediatR.INotificationHandler<BenchmarkNotification>.Handle(
BenchmarkNotification notification,
CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,154 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Order;
using System;
using System.Threading;
using System.Threading.Tasks;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Cqrs.Abstractions.Cqrs;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace GFramework.Cqrs.Benchmarks.Messaging;
/// <summary>
/// 对比单个 request 在直接调用、GFramework.CQRS runtime 与 MediatR 之间的 steady-state dispatch 开销。
/// </summary>
[Config(typeof(Config))]
public class RequestBenchmarks
{
private MicrosoftDiContainer _container = null!;
private ICqrsRuntime _runtime = null!;
private ServiceProvider _serviceProvider = null!;
private IMediator _mediatr = null!;
private BenchmarkRequestHandler _baselineHandler = null!;
private BenchmarkRequest _request = null!;
/// <summary>
/// 配置 request benchmark 的公共输出格式。
/// </summary>
private sealed class Config : ManualConfig
{
public Config()
{
AddJob(Job.Default);
AddColumnProvider(DefaultColumnProviders.Instance);
AddColumn(new CustomColumn("Scenario", static (_, _) => "Request"));
AddDiagnoser(MemoryDiagnoser.Default);
WithOrderer(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest, MethodOrderPolicy.Declared));
}
}
/// <summary>
/// 构建 request dispatch 所需的最小 runtime 宿主和对照对象。
/// </summary>
[GlobalSetup]
public void Setup()
{
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider
{
MinLevel = LogLevel.Fatal
};
Fixture.Setup("Request", handlerCount: 1, pipelineCount: 0);
_container = new MicrosoftDiContainer();
_baselineHandler = new BenchmarkRequestHandler();
_container.RegisterTransient<GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>, BenchmarkRequestHandler>();
_runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
_container,
LoggerFactoryResolver.Provider.CreateLogger(nameof(RequestBenchmarks)));
var services = new ServiceCollection();
services.AddSingleton<MediatR.IRequestHandler<BenchmarkRequest, BenchmarkResponse>, BenchmarkRequestHandler>();
services.AddMediatR(static options => options.RegisterServicesFromAssembly(typeof(RequestBenchmarks).Assembly));
_serviceProvider = services.BuildServiceProvider();
_mediatr = _serviceProvider.GetRequiredService<IMediator>();
_request = new BenchmarkRequest(Guid.NewGuid());
}
/// <summary>
/// 释放 MediatR 对照组使用的 DI 宿主。
/// </summary>
[GlobalCleanup]
public void Cleanup()
{
_serviceProvider.Dispose();
}
/// <summary>
/// 直接调用 handler作为 dispatch 额外开销的 baseline。
/// </summary>
[Benchmark(Baseline = true)]
public ValueTask<BenchmarkResponse> SendRequest_Baseline()
{
return _baselineHandler.Handle(_request, CancellationToken.None);
}
/// <summary>
/// 通过 GFramework.CQRS runtime 发送 request。
/// </summary>
[Benchmark]
public ValueTask<BenchmarkResponse> SendRequest_GFrameworkCqrs()
{
return _runtime.SendAsync(BenchmarkContext.Instance, _request, CancellationToken.None);
}
/// <summary>
/// 通过 MediatR 发送 request作为外部设计对照。
/// </summary>
[Benchmark]
public Task<BenchmarkResponse> SendRequest_MediatR()
{
return _mediatr.Send(_request, CancellationToken.None);
}
/// <summary>
/// Benchmark request。
/// </summary>
/// <param name="Id">请求标识。</param>
public sealed record BenchmarkRequest(Guid Id) :
GFramework.Cqrs.Abstractions.Cqrs.IRequest<BenchmarkResponse>,
MediatR.IRequest<BenchmarkResponse>;
/// <summary>
/// Benchmark response。
/// </summary>
/// <param name="Id">响应标识。</param>
public sealed record BenchmarkResponse(Guid Id);
/// <summary>
/// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 request handler。
/// </summary>
public sealed class BenchmarkRequestHandler :
GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>,
MediatR.IRequestHandler<BenchmarkRequest, BenchmarkResponse>
{
/// <summary>
/// 处理 GFramework.CQRS request。
/// </summary>
public ValueTask<BenchmarkResponse> Handle(BenchmarkRequest request, CancellationToken cancellationToken)
{
return ValueTask.FromResult(new BenchmarkResponse(request.Id));
}
/// <summary>
/// 处理 MediatR request。
/// </summary>
Task<BenchmarkResponse> MediatR.IRequestHandler<BenchmarkRequest, BenchmarkResponse>.Handle(
BenchmarkRequest request,
CancellationToken cancellationToken)
{
return Task.FromResult(new BenchmarkResponse(request.Id));
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
namespace GFramework.Cqrs.Benchmarks;
/// <summary>
/// 提供 GFramework.CQRS benchmark 的统一命令行入口。
/// </summary>
internal static class Program
{
/// <summary>
/// 运行当前程序集中的全部 benchmark。
/// </summary>
/// <param name="args">透传给 BenchmarkDotNet 的命令行参数。</param>
private static void Main(string[] args)
{
ConsoleLogger.Default.WriteLine("Running GFramework.Cqrs benchmarks");
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
}
}

View File

@ -0,0 +1,35 @@
# GFramework.Cqrs.Benchmarks
该模块承载 `GFramework.Cqrs` 的独立性能基准工程,用于持续比较运行时 dispatch、publish、cold-start 与后续 generator / pipeline 收口的成本变化。
## 目的
- 为 `GFramework.Cqrs` 建立独立于 NUnit 集成测试的 BenchmarkDotNet 基线
- 参考 `ai-libs/Mediator/benchmarks` 的场景组织方式,逐步补齐 request、notification、stream 与初始化成本对比
- 为后续吸收 `Mediator` 的 dispatch 设计、fixture 组织和对比矩阵提供可重复验证入口
## 当前内容
- `Program.cs`
- benchmark 命令行入口
- `Messaging/Fixture.cs`
- 运行前输出并校验场景配置
- `Messaging/RequestBenchmarks.cs`
- direct handler、`GFramework.Cqrs` runtime 与 `MediatR` 的 request steady-state dispatch 对比
- `Messaging/NotificationBenchmarks.cs`
- `GFramework.Cqrs` runtime 与 `MediatR` 的单处理器 notification publish 对比
## 最小使用方式
```bash
dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release
```
也可以通过 `BenchmarkDotNet` 过滤器只运行某一类场景。
## 后续扩展方向
- pipeline behavior 数量矩阵
- generated invoker provider 与纯反射 dispatch 对比
- stream request benchmark
- cold-start 与 registration 成本对比

View File

@ -2003,6 +2003,120 @@ public class CqrsHandlerRegistryGeneratorTests
} }
"""; """;
private const string MixedRequestInvokerProviderSource = """
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.DependencyInjection
{
public interface IServiceCollection { }
public static class ServiceCollectionServiceExtensions
{
public static void AddTransient(IServiceCollection services, Type serviceType, Type implementationType) { }
}
}
namespace GFramework.Core.Abstractions.Logging
{
public interface ILogger
{
void Debug(string msg);
}
}
namespace GFramework.Cqrs.Abstractions.Cqrs
{
public interface IRequest<TResponse> { }
public interface INotification { }
public interface IStreamRequest<TResponse> { }
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse>
{
ValueTask<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
}
namespace GFramework.Cqrs
{
public interface ICqrsHandlerRegistry
{
void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger);
}
public interface ICqrsRequestInvokerProvider
{
bool TryGetDescriptor(Type requestType, Type responseType, out CqrsRequestInvokerDescriptor? descriptor);
}
public interface IEnumeratesCqrsRequestInvokerDescriptors
{
IReadOnlyList<CqrsRequestInvokerDescriptorEntry> GetDescriptors();
}
public sealed class CqrsRequestInvokerDescriptor
{
public CqrsRequestInvokerDescriptor(Type handlerType, MethodInfo invokerMethod) { }
}
public sealed class CqrsRequestInvokerDescriptorEntry
{
public CqrsRequestInvokerDescriptorEntry(Type requestType, Type responseType, CqrsRequestInvokerDescriptor descriptor)
{
RequestType = requestType;
ResponseType = responseType;
Descriptor = descriptor;
}
public Type RequestType { get; }
public Type ResponseType { get; }
public CqrsRequestInvokerDescriptor Descriptor { get; }
}
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class CqrsHandlerRegistryAttribute : Attribute
{
public CqrsHandlerRegistryAttribute(Type registryType) { }
}
}
namespace TestApp
{
using GFramework.Cqrs.Abstractions.Cqrs;
public sealed record AlphaRequest() : IRequest<string>;
public sealed record BetaRequest() : IRequest<int>;
public sealed class AlphaHandler : IRequestHandler<AlphaRequest, string>
{
public ValueTask<string> Handle(AlphaRequest request, CancellationToken cancellationToken)
{
return ValueTask.FromResult("alpha");
}
}
public sealed class Container
{
private sealed class HiddenBetaHandler : IRequestHandler<BetaRequest, int>
{
public ValueTask<int> Handle(BetaRequest request, CancellationToken cancellationToken)
{
return ValueTask.FromResult(42);
}
}
}
}
""";
private const string HiddenImplementationStreamInvokerProviderSource = """ private const string HiddenImplementationStreamInvokerProviderSource = """
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -2108,6 +2222,122 @@ public class CqrsHandlerRegistryGeneratorTests
} }
"""; """;
private const string MixedStreamInvokerProviderSource = """
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.DependencyInjection
{
public interface IServiceCollection { }
public static class ServiceCollectionServiceExtensions
{
public static void AddTransient(IServiceCollection services, Type serviceType, Type implementationType) { }
}
}
namespace GFramework.Core.Abstractions.Logging
{
public interface ILogger
{
void Debug(string msg);
}
}
namespace GFramework.Cqrs.Abstractions.Cqrs
{
public interface IRequest<TResponse> { }
public interface INotification { }
public interface IStreamRequest<TResponse> { }
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse>
{
IAsyncEnumerable<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}
}
namespace GFramework.Cqrs
{
public interface ICqrsHandlerRegistry
{
void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger);
}
public interface ICqrsStreamInvokerProvider
{
bool TryGetDescriptor(Type requestType, Type responseType, out CqrsStreamInvokerDescriptor? descriptor);
}
public interface IEnumeratesCqrsStreamInvokerDescriptors
{
IReadOnlyList<CqrsStreamInvokerDescriptorEntry> GetDescriptors();
}
public sealed class CqrsStreamInvokerDescriptor
{
public CqrsStreamInvokerDescriptor(Type handlerType, MethodInfo invokerMethod) { }
}
public sealed class CqrsStreamInvokerDescriptorEntry
{
public CqrsStreamInvokerDescriptorEntry(Type requestType, Type responseType, CqrsStreamInvokerDescriptor descriptor)
{
RequestType = requestType;
ResponseType = responseType;
Descriptor = descriptor;
}
public Type RequestType { get; }
public Type ResponseType { get; }
public CqrsStreamInvokerDescriptor Descriptor { get; }
}
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class CqrsHandlerRegistryAttribute : Attribute
{
public CqrsHandlerRegistryAttribute(Type registryType) { }
}
}
namespace TestApp
{
using GFramework.Cqrs.Abstractions.Cqrs;
public sealed record AlphaStream() : IStreamRequest<int>;
public sealed record BetaStream() : IStreamRequest<string>;
public sealed class AlphaStreamHandler : IStreamRequestHandler<AlphaStream, int>
{
public async IAsyncEnumerable<int> Handle(AlphaStream request, CancellationToken cancellationToken)
{
yield return 1;
await Task.CompletedTask;
}
}
public sealed class Container
{
private sealed class HiddenBetaStreamHandler : IStreamRequestHandler<BetaStream, string>
{
public async IAsyncEnumerable<string> Handle(BetaStream request, CancellationToken cancellationToken)
{
yield return "beta";
await Task.CompletedTask;
}
}
}
}
""";
private const string PreciseReflectedRequestInvokerProviderBoundarySource = """ private const string PreciseReflectedRequestInvokerProviderBoundarySource = """
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -3052,6 +3282,40 @@ public class CqrsHandlerRegistryGeneratorTests
}); });
} }
/// <summary>
/// 验证当同一轮生成同时包含 direct registration 与“隐藏实现类型 + 可见 handler interface”注册时
/// request invoker provider 会按稳定实现排序生成连续描述符和方法编号。
/// </summary>
[Test]
public void Emits_Request_Invoker_Provider_Metadata_In_Stable_Order_For_Mixed_Direct_And_Reflected_Implementations()
{
var generatedSource = RunGenerator(MixedRequestInvokerProviderSource);
Assert.Multiple(() =>
{
Assert.That(
generatedSource,
Does.Contain(
"new global::GFramework.Cqrs.CqrsRequestInvokerDescriptorEntry(typeof(global::TestApp.AlphaRequest), typeof(string), new global::GFramework.Cqrs.CqrsRequestInvokerDescriptor(typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::TestApp.AlphaRequest, string>), typeof(__GFrameworkGeneratedCqrsHandlerRegistry).GetMethod(nameof(InvokeRequestHandler0), global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static)!))"));
Assert.That(
generatedSource,
Does.Contain(
"new global::GFramework.Cqrs.CqrsRequestInvokerDescriptorEntry(typeof(global::TestApp.BetaRequest), typeof(int), new global::GFramework.Cqrs.CqrsRequestInvokerDescriptor(typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::TestApp.BetaRequest, int>), typeof(__GFrameworkGeneratedCqrsHandlerRegistry).GetMethod(nameof(InvokeRequestHandler1), global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static)!))"));
Assert.That(
generatedSource,
Does.Contain(
"private static global::System.Threading.Tasks.ValueTask<string> InvokeRequestHandler0(object handler, object request, global::System.Threading.CancellationToken cancellationToken)"));
Assert.That(
generatedSource,
Does.Contain(
"private static global::System.Threading.Tasks.ValueTask<int> InvokeRequestHandler1(object handler, object request, global::System.Threading.CancellationToken cancellationToken)"));
Assert.That(
generatedSource,
Does.Contain(
"var typedHandler = (global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::TestApp.BetaRequest, int>)handler;"));
});
}
/// <summary> /// <summary>
/// 验证当 runtime 缺少 <c>ICqrsRequestInvokerProvider</c> 时, /// 验证当 runtime 缺少 <c>ICqrsRequestInvokerProvider</c> 时,
/// 生成器会整体跳过 request invoker provider 元数据发射,而不是输出半套 descriptor 成员。 /// 生成器会整体跳过 request invoker provider 元数据发射,而不是输出半套 descriptor 成员。
@ -3265,6 +3529,40 @@ public class CqrsHandlerRegistryGeneratorTests
}); });
} }
/// <summary>
/// 验证当同一轮生成同时包含 direct registration 与“隐藏实现类型 + 可见 handler interface”注册时
/// stream invoker provider 会按稳定实现排序生成连续描述符和方法编号。
/// </summary>
[Test]
public void Emits_Stream_Invoker_Provider_Metadata_In_Stable_Order_For_Mixed_Direct_And_Reflected_Implementations()
{
var generatedSource = RunGenerator(MixedStreamInvokerProviderSource);
Assert.Multiple(() =>
{
Assert.That(
generatedSource,
Does.Contain(
"new global::GFramework.Cqrs.CqrsStreamInvokerDescriptorEntry(typeof(global::TestApp.AlphaStream), typeof(int), new global::GFramework.Cqrs.CqrsStreamInvokerDescriptor(typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler<global::TestApp.AlphaStream, int>), typeof(__GFrameworkGeneratedCqrsHandlerRegistry).GetMethod(nameof(InvokeStreamHandler0), global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static)!))"));
Assert.That(
generatedSource,
Does.Contain(
"new global::GFramework.Cqrs.CqrsStreamInvokerDescriptorEntry(typeof(global::TestApp.BetaStream), typeof(string), new global::GFramework.Cqrs.CqrsStreamInvokerDescriptor(typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler<global::TestApp.BetaStream, string>), typeof(__GFrameworkGeneratedCqrsHandlerRegistry).GetMethod(nameof(InvokeStreamHandler1), global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static)!))"));
Assert.That(
generatedSource,
Does.Contain(
"private static object InvokeStreamHandler0(object handler, object request, global::System.Threading.CancellationToken cancellationToken)"));
Assert.That(
generatedSource,
Does.Contain(
"private static object InvokeStreamHandler1(object handler, object request, global::System.Threading.CancellationToken cancellationToken)"));
Assert.That(
generatedSource,
Does.Contain(
"var typedHandler = (global::GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler<global::TestApp.BetaStream, string>)handler;"));
});
}
/// <summary> /// <summary>
/// 验证当 runtime 缺少 <c>ICqrsStreamInvokerProvider</c> 时, /// 验证当 runtime 缺少 <c>ICqrsStreamInvokerProvider</c> 时,
/// 生成器会整体跳过 stream invoker provider 元数据发射,而不是保留孤立的 descriptor 成员。 /// 生成器会整体跳过 stream invoker provider 元数据发射,而不是保留孤立的 descriptor 成员。

View File

@ -74,6 +74,7 @@
<None Remove="GFramework.Godot.Tests\**"/> <None Remove="GFramework.Godot.Tests\**"/>
<None Remove="GFramework.Cqrs\**"/> <None Remove="GFramework.Cqrs\**"/>
<None Remove="GFramework.Cqrs.Abstractions\**"/> <None Remove="GFramework.Cqrs.Abstractions\**"/>
<None Remove="GFramework.Cqrs.Benchmarks\**"/>
<None Remove="GFramework.Cqrs.Tests\**"/> <None Remove="GFramework.Cqrs.Tests\**"/>
<None Remove="GFramework.Tests.Common\**"/> <None Remove="GFramework.Tests.Common\**"/>
<None Remove="ai-libs\**" /> <None Remove="ai-libs\**" />
@ -123,6 +124,7 @@
<Compile Remove="GFramework.Godot.Tests\**" /> <Compile Remove="GFramework.Godot.Tests\**" />
<Compile Remove="GFramework.Cqrs\**" /> <Compile Remove="GFramework.Cqrs\**" />
<Compile Remove="GFramework.Cqrs.Abstractions\**" /> <Compile Remove="GFramework.Cqrs.Abstractions\**" />
<Compile Remove="GFramework.Cqrs.Benchmarks\**" />
<Compile Remove="GFramework.Cqrs.Tests\**" /> <Compile Remove="GFramework.Cqrs.Tests\**" />
<Compile Remove="GFramework.Tests.Common\**" /> <Compile Remove="GFramework.Tests.Common\**" />
<Compile Remove="ai-libs\**" /> <Compile Remove="ai-libs\**" />
@ -158,6 +160,7 @@
<EmbeddedResource Remove="GFramework.Godot.Tests\**" /> <EmbeddedResource Remove="GFramework.Godot.Tests\**" />
<EmbeddedResource Remove="GFramework.Cqrs\**" /> <EmbeddedResource Remove="GFramework.Cqrs\**" />
<EmbeddedResource Remove="GFramework.Cqrs.Abstractions\**" /> <EmbeddedResource Remove="GFramework.Cqrs.Abstractions\**" />
<EmbeddedResource Remove="GFramework.Cqrs.Benchmarks\**" />
<EmbeddedResource Remove="GFramework.Cqrs.Tests\**" /> <EmbeddedResource Remove="GFramework.Cqrs.Tests\**" />
<EmbeddedResource Remove="GFramework.Tests.Common\**" /> <EmbeddedResource Remove="GFramework.Tests.Common\**" />
<EmbeddedResource Remove="ai-libs\**" /> <EmbeddedResource Remove="ai-libs\**" />

View File

@ -50,6 +50,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Cqrs.SourceGener
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Game.SourceGenerators", "GFramework.Game.SourceGenerators\GFramework.Game.SourceGenerators.csproj", "{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Game.SourceGenerators", "GFramework.Game.SourceGenerators\GFramework.Game.SourceGenerators.csproj", "{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Cqrs.Benchmarks", "GFramework.Cqrs.Benchmarks\GFramework.Cqrs.Benchmarks.csproj", "{5609D017-E481-431B-874A-7D06BFF698F9}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -360,6 +362,18 @@ Global
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x64.Build.0 = Release|Any CPU {9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x64.Build.0 = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x86.ActiveCfg = Release|Any CPU {9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x86.ActiveCfg = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x86.Build.0 = Release|Any CPU {9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x86.Build.0 = Release|Any CPU
{5609D017-E481-431B-874A-7D06BFF698F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5609D017-E481-431B-874A-7D06BFF698F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5609D017-E481-431B-874A-7D06BFF698F9}.Debug|x64.ActiveCfg = Debug|Any CPU
{5609D017-E481-431B-874A-7D06BFF698F9}.Debug|x64.Build.0 = Debug|Any CPU
{5609D017-E481-431B-874A-7D06BFF698F9}.Debug|x86.ActiveCfg = Debug|Any CPU
{5609D017-E481-431B-874A-7D06BFF698F9}.Debug|x86.Build.0 = Debug|Any CPU
{5609D017-E481-431B-874A-7D06BFF698F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5609D017-E481-431B-874A-7D06BFF698F9}.Release|Any CPU.Build.0 = Release|Any CPU
{5609D017-E481-431B-874A-7D06BFF698F9}.Release|x64.ActiveCfg = Release|Any CPU
{5609D017-E481-431B-874A-7D06BFF698F9}.Release|x64.Build.0 = Release|Any CPU
{5609D017-E481-431B-874A-7D06BFF698F9}.Release|x86.ActiveCfg = Release|Any CPU
{5609D017-E481-431B-874A-7D06BFF698F9}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -7,7 +7,7 @@ CQRS 迁移与收敛。
## 当前恢复点 ## 当前恢复点
- 恢复点编号:`CQRS-REWRITE-RP-082` - 恢复点编号:`CQRS-REWRITE-RP-084`
- 当前阶段:`Phase 8` - 当前阶段:`Phase 8`
- 当前 PR 锚点:`PR #323` - 当前 PR 锚点:`PR #323`
- 当前结论: - 当前结论:
@ -18,6 +18,8 @@ CQRS 迁移与收敛。
- `RP-080` 已将基础 generation gate 回归扩展到 notification handler interface、stream handler interface 与 registry attribute 缺失分支 - `RP-080` 已将基础 generation gate 回归扩展到 notification handler interface、stream handler interface 与 registry attribute 缺失分支
- `RP-081` 已继续补齐基础 generation gate 的 logging 与 DI runtime contract 缺失分支 - `RP-081` 已继续补齐基础 generation gate 的 logging 与 DI runtime contract 缺失分支
- 当前 `RP-082` 已补齐基础 generation gate 的 request handler runtime contract 缺失分支 - 当前 `RP-082` 已补齐基础 generation gate 的 request handler runtime contract 缺失分支
- `RP-083` 已补齐 mixed direct / reflected-implementation request 与 stream invoker provider 发射顺序回归
- 当前 `RP-084` 已引入独立 `GFramework.Cqrs.Benchmarks` 项目,作为持续吸收 `Mediator` benchmark 组织方式的第一落点
- `ai-plan` active 入口现以 `PR #323``RP-082` 为唯一权威恢复锚点;`PR #307`、其他更早 PR 与阶段细节均以下方归档或说明为准 - `ai-plan` active 入口现以 `PR #323``RP-082` 为唯一权威恢复锚点;`PR #307`、其他更早 PR 与阶段细节均以下方归档或说明为准
## 当前活跃事实 ## 当前活跃事实
@ -45,6 +47,14 @@ CQRS 迁移与收敛。
- 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行 - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release` - `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release`
- 结果:通过,`0 warning / 0 error` - 结果:通过,`0 warning / 0 error`
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_In_Stable_Order_For_Mixed_Direct_And_Reflected_Implementations|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_In_Stable_Order_For_Mixed_Direct_And_Reflected_Implementations"`
- 结果:通过,`2/2` passed
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `GIT_DIR=<worktree-git-dir> GIT_WORK_TREE=<worktree-root> python3 scripts/license-header.py --check`
- 结果:通过
- `git diff --check`
- 结果:通过
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Provider_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Enumerator|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Entry_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"` - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Provider_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Enumerator|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Entry_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
- 结果:通过,`5/5` passed - 结果:通过,`5/5` passed
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Entry_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Provider_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Enumerator"` - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Entry_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Provider_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Enumerator"`
@ -93,8 +103,8 @@ CQRS 迁移与收敛。
## 下一推荐步骤 ## 下一推荐步骤
1. 继续处理 `PR #323` 的剩余 review 收尾,优先保持 `ai-plan` active 入口与 trace 的单一锚点一致 1. 继续处理 `PR #323` 的剩余 review 收尾,优先保持 `ai-plan` active 入口与 trace 的单一锚点一致
2. 若继续推进代码切片,优先复核基础 generation gate 之外的 runtime contract 或 fallback selection 分支;基础 gate 的可安全构造缺失分支已覆盖 2. 若继续推进“吸收 Mediator 设计哲学”的切片,优先扩展 benchmark 场景矩阵到 pipeline、stream、cold-start 与 generated invoker provider 对照
3. 在进入下一批 runtime / generator 收敛前,保持最小 Release build 或 targeted test 作为权威验证 3. 在进入下一批 runtime / generator 收敛前,保持最小 Release build、targeted test 或 benchmark project build 作为权威验证
## 活跃文档 ## 活跃文档

View File

@ -212,3 +212,87 @@
1. 若 review 重新触发后仍有 latest-head open thread继续以 `PR #323` 为当前唯一 PR 恢复锚点复核 1. 若 review 重新触发后仍有 latest-head open thread继续以 `PR #323` 为当前唯一 PR 恢复锚点复核
2. 后续若继续推进代码切片,优先复核基础 generation gate 之外的 runtime contract 或 fallback selection 分支 2. 后续若继续推进代码切片,优先复核基础 generation gate 之外的 runtime contract 或 fallback selection 分支
## 2026-05-06
### 阶段mixed invoker provider 排序回归CQRS-REWRITE-RP-083
- 使用 `$gframework-batch-boot 50` 继续 `feat/cqrs-optimization` 的 CQRS 收口批次
- 批次目标:在 branch diff 相对 `origin/main` 接近 `50` 个文件前,继续补齐低风险的 generator runtime contract / emission 回归
- 本轮基线选择:
- `origin/main a8c6c11e`committer date `2026-05-05 13:14:24 +0800`
- `main a8c6c11e`committer date `2026-05-05 13:14:24 +0800`
- 当前分支 `feat/cqrs-optimization a8c6c11e`committer date `2026-05-05 13:14:24 +0800`
- 启动时 branch diff vs `origin/main``0` files / `0` lines因此继续选择低风险测试回归切片
- 本轮复核 `CreateGeneratedRegistrySourceShape` 与 invoker emission 路径后确认:
- 现有测试已覆盖 request / stream provider 的单一 direct 场景、单一 reflected-implementation 场景、precise reflected 跳过边界,以及各项 runtime contract 缺失分支
- 尚未锁定“同一 registry 同时包含 direct registration 与 reflected-implementation registration”时的 descriptor 顺序与 `Invoke*HandlerN` 编号稳定性
- 已补齐:
- `Emits_Request_Invoker_Provider_Metadata_In_Stable_Order_For_Mixed_Direct_And_Reflected_Implementations`
- `Emits_Stream_Invoker_Provider_Metadata_In_Stable_Order_For_Mixed_Direct_And_Reflected_Implementations`
- 两组 source fixture`MixedRequestInvokerProviderSource``MixedStreamInvokerProviderSource`
- 通过新增回归,显式锁定以下约束:
- provider descriptor 条目按稳定实现排序输出
- `InvokeRequestHandler0/1``InvokeStreamHandler0/1` 的方法编号随 emission 顺序连续增长
- 隐藏实现类型不会破坏 direct registration 与 reflected-implementation registration 的混合发射
### 验证RP-083
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_In_Stable_Order_For_Mixed_Direct_And_Reflected_Implementations|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_In_Stable_Order_For_Mixed_Direct_And_Reflected_Implementations"`
- 结果:通过,`2/2` passed
### 当前 stop-condition 度量RP-083
- primary metricbranch diff files vs `origin/main`
- 当前说明active batch 尚未提交时,基于 `HEAD` 的 branch diff 仍显示 `0` files / `0` lines提交本批后再以新 `HEAD` 复算累计 branch diff
### 当前下一步RP-083
1. 提交本轮 mixed invoker provider 排序回归后,复算 branch diff vs `origin/main`,确认 `50` 文件阈值仍有充足余量
2. 若继续推进代码切片,优先复核 invoker provider 之外的 runtime contract 或 fallback selection 分支
### 阶段benchmark 基础设施引入CQRS-REWRITE-RP-084
- 用户明确将当前长期分支目标上提为:系统性吸收 `ai-libs/Mediator` 的实现思路与设计哲学,并将可取部分纳入 `GFramework.Cqrs`
- 本轮据此调整批次目标,不再把关注点收缩到单个 generator 回归,而是建立能持续比较和吸收设计差异的 benchmark 基础设施
- 参考 `ai-libs/Mediator` 的 benchmark 设计后,本轮采纳的核心结构包括:
- 独立 benchmark 项目壳,而非扩展现有 NUnit 测试项目
- 共享 `Fixture` 输出并校验场景配置
- `Request` / `Notification` 两个 messaging 场景作为首批最小落点
- 自定义列 `CustomColumn`,为后续矩阵扩展保留可读结果标签
- 本轮新增:
- `GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj`
- `GFramework.Cqrs.Benchmarks/Program.cs`
- `GFramework.Cqrs.Benchmarks/CustomColumn.cs`
- `GFramework.Cqrs.Benchmarks/Messaging/Fixture.cs`
- `GFramework.Cqrs.Benchmarks/Messaging/BenchmarkContext.cs`
- `GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs`
- `GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs`
- `GFramework.Cqrs.Benchmarks/README.md`
- 设计取舍:
- 使用最小 `ICqrsContext` marker避免把完整 `ArchitectureContext` 初始化成本混入 steady-state dispatch
- 直接复用 `GFramework.Cqrs.CqrsRuntimeFactory``MicrosoftDiContainer`,让基准聚焦于 runtime dispatch / publish
- 外部对照组先接入 `MediatR`,保持与 `Mediator` benchmark 的对照哲学一致;但本轮仍只做最小 request / notification 场景
- 暂不把 source generator benchmark、cold-start 独立工程或完整 pipeline / stream 矩阵一起引入,避免首批 scope 失控
- 兼容性修正:
- 在根 `GFramework.csproj` 中显式排除 `GFramework.Cqrs.Benchmarks/**`,避免 meta-package 意外编译 benchmark 源码
- 将 benchmark 项目加入 `GFramework.sln`,保持仓库级工作流完整
### 验证RP-084
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `GIT_DIR=<worktree-git-dir> GIT_WORK_TREE=<worktree-root> python3 scripts/license-header.py --check`
- 结果:通过
- `git diff --check`
- 结果:通过
### 当前 stop-condition 度量RP-084
- primary metricbranch diff files vs `origin/main`
- 当前说明:本轮仍在 `50` 文件阈值以内,可继续按 benchmark 场景或 CQRS runtime 对照能力分批推进
### 当前下一步RP-084
1. 继续扩展 `GFramework.Cqrs.Benchmarks`,优先补齐 pipeline、stream、cold-start 与 generated invoker provider 对照场景
2. 当后续有具体 runtime 优化切片时,用该 benchmark 项目验证是否真正吸收到了 `Mediator` 的低开销 dispatch 设计收益