mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-11 20:38:58 +08:00
fix(cqrs): 收口 PR review 剩余问题
- 修复 NotificationLifetimeBenchmarks 的 scoped 容器释放与公开 XML 契约缺口 - 修复 generated descriptor 预热阶段先去重后校验导致的有效后继条目丢失问题 - 更新 generated descriptor 的 MethodInfo 比较方式并补充 request/stream 回归测试 - 同步 cqrs-rewrite active tracking 与 trace 的当前 PR 锚点到 PR #348
This commit is contained in:
parent
babd132e81
commit
4e98b63e9c
@ -133,7 +133,7 @@ public class NotificationLifetimeBenchmarks
|
||||
{
|
||||
try
|
||||
{
|
||||
BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider);
|
||||
BenchmarkCleanupHelper.DisposeAll(_scopedContainer, _container, _serviceProvider);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -144,6 +144,7 @@ public class NotificationLifetimeBenchmarks
|
||||
/// <summary>
|
||||
/// 直接调用 handler,作为不同生命周期矩阵下的 publish 额外开销 baseline。
|
||||
/// </summary>
|
||||
/// <returns>代表基线 handler 完成当前 notification 处理的值任务。</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ValueTask PublishNotification_Baseline()
|
||||
{
|
||||
@ -153,6 +154,7 @@ public class NotificationLifetimeBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 GFramework.CQRS runtime 发布 notification。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 GFramework.CQRS publish 完成的值任务。</returns>
|
||||
[Benchmark]
|
||||
public ValueTask PublishNotification_GFrameworkCqrs()
|
||||
{
|
||||
@ -171,6 +173,7 @@ public class NotificationLifetimeBenchmarks
|
||||
/// <summary>
|
||||
/// 通过 MediatR 发布 notification,作为外部对照。
|
||||
/// </summary>
|
||||
/// <returns>代表当前 MediatR publish 完成的任务。</returns>
|
||||
[Benchmark]
|
||||
public Task PublishNotification_MediatR()
|
||||
{
|
||||
@ -293,6 +296,9 @@ public class NotificationLifetimeBenchmarks
|
||||
/// <summary>
|
||||
/// 处理 GFramework.CQRS notification。
|
||||
/// </summary>
|
||||
/// <param name="notification">当前要处理的 notification。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>代表当前 notification 处理完成的值任务。</returns>
|
||||
public ValueTask Handle(BenchmarkNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(notification);
|
||||
|
||||
@ -548,6 +548,26 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
|
||||
Assert.That(response, Is.EqualTo("runtime:payload"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当首个 request descriptor 无效、后续同键 descriptor 有效时,
|
||||
/// registrar 不会因为过早去重而丢掉本可注册的 generated descriptor。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task SendAsync_Should_Use_Later_Valid_Generated_Request_Descriptor_When_First_Duplicate_Is_Invalid()
|
||||
{
|
||||
var generatedAssembly = CreateGeneratedAssembly(
|
||||
typeof(InvalidThenValidDuplicateRequestInvokerProviderRegistry),
|
||||
"GFramework.Cqrs.Tests.Cqrs.InvalidThenValidDuplicateRequestInvokerAssembly, Version=1.0.0.0");
|
||||
var container = new MicrosoftDiContainer();
|
||||
|
||||
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
|
||||
container.Freeze();
|
||||
|
||||
var context = new ArchitectureContext(container);
|
||||
var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false);
|
||||
Assert.That(response, Is.EqualTo("generated:payload"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当 stream descriptor 枚举项与 provider 的 TryGetDescriptor 结果不一致时,
|
||||
/// registrar 会忽略该坏 descriptor,并继续回退到反射建流路径。
|
||||
@ -568,6 +588,26 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
|
||||
Assert.That(results, Is.EqualTo([3, 4]));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当首个 stream descriptor 无效、后续同键 descriptor 有效时,
|
||||
/// registrar 不会因为过早去重而丢掉本可注册的 generated descriptor。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task CreateStream_Should_Use_Later_Valid_Generated_Stream_Descriptor_When_First_Duplicate_Is_Invalid()
|
||||
{
|
||||
var generatedAssembly = CreateGeneratedAssembly(
|
||||
typeof(InvalidThenValidDuplicateStreamInvokerProviderRegistry),
|
||||
"GFramework.Cqrs.Tests.Cqrs.InvalidThenValidDuplicateStreamInvokerAssembly, Version=1.0.0.0");
|
||||
var container = new MicrosoftDiContainer();
|
||||
|
||||
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
|
||||
container.Freeze();
|
||||
|
||||
var context = new ArchitectureContext(container);
|
||||
var results = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false);
|
||||
Assert.That(results, Is.EqualTo([30, 31]));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟返回实例 request invoker 方法的 generated registry。
|
||||
/// </summary>
|
||||
@ -1288,6 +1328,81 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟首个 request descriptor 无效、后续同键 descriptor 有效的 generated registry。
|
||||
/// </summary>
|
||||
private sealed class InvalidThenValidDuplicateRequestInvokerProviderRegistry :
|
||||
ICqrsHandlerRegistry,
|
||||
ICqrsRequestInvokerProvider,
|
||||
IEnumeratesCqrsRequestInvokerDescriptors
|
||||
{
|
||||
private static readonly CqrsRequestInvokerDescriptor InvalidDescriptor = new(
|
||||
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
|
||||
typeof(InvalidThenValidDuplicateRequestInvokerProviderRegistry).GetMethod(
|
||||
nameof(InvokeAlternativeGenerated),
|
||||
BindingFlags.NonPublic | BindingFlags.Static)!);
|
||||
|
||||
private static readonly CqrsRequestInvokerDescriptor ValidDescriptor = new(
|
||||
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
|
||||
typeof(GeneratedRequestInvokerProviderRegistry).GetMethod(
|
||||
"InvokeGenerated",
|
||||
BindingFlags.NonPublic | BindingFlags.Static)!);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register(IServiceCollection services, ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
services.AddTransient(
|
||||
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
|
||||
typeof(GeneratedRequestInvokerRequestHandler));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetDescriptor(
|
||||
Type requestType,
|
||||
Type responseType,
|
||||
out CqrsRequestInvokerDescriptor? descriptor)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(requestType);
|
||||
ArgumentNullException.ThrowIfNull(responseType);
|
||||
|
||||
if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string))
|
||||
{
|
||||
descriptor = ValidDescriptor;
|
||||
return true;
|
||||
}
|
||||
|
||||
descriptor = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<CqrsRequestInvokerDescriptorEntry> GetDescriptors()
|
||||
{
|
||||
return
|
||||
[
|
||||
new CqrsRequestInvokerDescriptorEntry(
|
||||
typeof(GeneratedRequestInvokerRequest),
|
||||
typeof(string),
|
||||
InvalidDescriptor),
|
||||
new CqrsRequestInvokerDescriptorEntry(
|
||||
typeof(GeneratedRequestInvokerRequest),
|
||||
typeof(string),
|
||||
ValidDescriptor)
|
||||
];
|
||||
}
|
||||
|
||||
private static ValueTask<string> InvokeAlternativeGenerated(
|
||||
object handler,
|
||||
object request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return ValueTask.FromResult("invalid-first:payload");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟枚举出的 stream descriptor 与 provider 显式查询结果不一致的 generated registry。
|
||||
/// </summary>
|
||||
@ -1356,6 +1471,78 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟首个 stream descriptor 无效、后续同键 descriptor 有效的 generated registry。
|
||||
/// </summary>
|
||||
private sealed class InvalidThenValidDuplicateStreamInvokerProviderRegistry :
|
||||
ICqrsHandlerRegistry,
|
||||
ICqrsStreamInvokerProvider,
|
||||
IEnumeratesCqrsStreamInvokerDescriptors
|
||||
{
|
||||
private static readonly CqrsStreamInvokerDescriptor InvalidDescriptor = new(
|
||||
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
|
||||
typeof(InvalidThenValidDuplicateStreamInvokerProviderRegistry).GetMethod(
|
||||
nameof(InvokeAlternativeGenerated),
|
||||
BindingFlags.NonPublic | BindingFlags.Static)!);
|
||||
|
||||
private static readonly CqrsStreamInvokerDescriptor ValidDescriptor = new(
|
||||
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
|
||||
typeof(GeneratedStreamInvokerProviderRegistry).GetMethod(
|
||||
"InvokeGenerated",
|
||||
BindingFlags.NonPublic | BindingFlags.Static)!);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register(IServiceCollection services, ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
services.AddTransient(
|
||||
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
|
||||
typeof(GeneratedStreamInvokerRequestHandler));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetDescriptor(
|
||||
Type requestType,
|
||||
Type responseType,
|
||||
out CqrsStreamInvokerDescriptor? descriptor)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(requestType);
|
||||
ArgumentNullException.ThrowIfNull(responseType);
|
||||
|
||||
if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int))
|
||||
{
|
||||
descriptor = ValidDescriptor;
|
||||
return true;
|
||||
}
|
||||
|
||||
descriptor = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<CqrsStreamInvokerDescriptorEntry> GetDescriptors()
|
||||
{
|
||||
return
|
||||
[
|
||||
new CqrsStreamInvokerDescriptorEntry(
|
||||
typeof(GeneratedStreamInvokerRequest),
|
||||
typeof(int),
|
||||
InvalidDescriptor),
|
||||
new CqrsStreamInvokerDescriptorEntry(
|
||||
typeof(GeneratedStreamInvokerRequest),
|
||||
typeof(int),
|
||||
ValidDescriptor)
|
||||
];
|
||||
}
|
||||
|
||||
private static object InvokeAlternativeGenerated(object handler, object request, CancellationToken cancellationToken)
|
||||
{
|
||||
return new[] { 800, 801 }.ToAsyncEnumerable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建带有 generated request invoker registry 元数据的程序集替身。
|
||||
/// </summary>
|
||||
|
||||
@ -360,6 +360,10 @@ internal static class CqrsHandlerRegistrar
|
||||
var descriptorKey = new InvokerDescriptorKey(
|
||||
descriptorEntry.RequestType,
|
||||
descriptorEntry.ResponseType);
|
||||
|
||||
if (!TryValidateEnumeratedRequestInvokerDescriptor(provider, descriptorEntry, assemblyName, logger))
|
||||
continue;
|
||||
|
||||
if (!registeredKeys.Add(descriptorKey))
|
||||
{
|
||||
logger.Warn(
|
||||
@ -367,9 +371,6 @@ internal static class CqrsHandlerRegistrar
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryValidateEnumeratedRequestInvokerDescriptor(provider, descriptorEntry, assemblyName, logger))
|
||||
continue;
|
||||
|
||||
CqrsDispatcher.RegisterGeneratedRequestInvokerDescriptor(
|
||||
descriptorEntry.RequestType,
|
||||
descriptorEntry.ResponseType,
|
||||
@ -455,6 +456,10 @@ internal static class CqrsHandlerRegistrar
|
||||
var descriptorKey = new InvokerDescriptorKey(
|
||||
descriptorEntry.RequestType,
|
||||
descriptorEntry.ResponseType);
|
||||
|
||||
if (!TryValidateEnumeratedStreamInvokerDescriptor(provider, descriptorEntry, assemblyName, logger))
|
||||
continue;
|
||||
|
||||
if (!registeredKeys.Add(descriptorKey))
|
||||
{
|
||||
logger.Warn(
|
||||
@ -462,9 +467,6 @@ internal static class CqrsHandlerRegistrar
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryValidateEnumeratedStreamInvokerDescriptor(provider, descriptorEntry, assemblyName, logger))
|
||||
continue;
|
||||
|
||||
CqrsDispatcher.RegisterGeneratedStreamInvokerDescriptor(
|
||||
descriptorEntry.RequestType,
|
||||
descriptorEntry.ResponseType,
|
||||
@ -501,7 +503,7 @@ internal static class CqrsHandlerRegistrar
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReferenceEquals(resolvedDescriptor.InvokerMethod, descriptorEntry.Descriptor.InvokerMethod) ||
|
||||
if (!resolvedDescriptor.InvokerMethod.Equals(descriptorEntry.Descriptor.InvokerMethod) ||
|
||||
resolvedDescriptor.HandlerType != descriptorEntry.Descriptor.HandlerType)
|
||||
{
|
||||
logger.Warn(
|
||||
@ -546,7 +548,7 @@ internal static class CqrsHandlerRegistrar
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReferenceEquals(resolvedDescriptor.InvokerMethod, descriptorEntry.Descriptor.InvokerMethod) ||
|
||||
if (!resolvedDescriptor.InvokerMethod.Equals(descriptorEntry.Descriptor.InvokerMethod) ||
|
||||
resolvedDescriptor.HandlerType != descriptorEntry.Descriptor.HandlerType)
|
||||
{
|
||||
logger.Warn(
|
||||
|
||||
@ -12,9 +12,9 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-133`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-134`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前 PR 锚点:`PR #347`
|
||||
- 当前 PR 锚点:`PR #348`
|
||||
- 当前结论:
|
||||
- 本轮按 `$gframework-batch-boot` 协调多波 non-conflicting subagent,基线固定为
|
||||
`origin/main @ 3b2e6899d5ffdcfb634b28f3846f57528fbf9196 (2026-05-11T12:25:00+08:00)`。
|
||||
@ -35,6 +35,11 @@ CQRS 迁移与收敛。
|
||||
- `docs/zh-CN/core/cqrs.md`
|
||||
- `docs/zh-CN/source-generators/cqrs-handler-registry-generator.md`
|
||||
- `ai-plan/public/cqrs-rewrite/archive/**` 顶部导航与跳转约定
|
||||
- 当前 `PR #348` latest-head review 再次复核后:
|
||||
- 跳过 `NotificationLifetimeBenchmarks.HandlerLifetime` 的 `[GenerateEnumExtensions]` 建议,原因是仓库没有“所有枚举统一生成扩展”的约定,且 benchmark 局部枚举不在该能力的强制范围内
|
||||
- 接受并修复 `NotificationLifetimeBenchmarks` 的 scoped 容器释放与公开 XML 文档缺口
|
||||
- 接受并修复 `CqrsHandlerRegistrar` 对 generated descriptor 的“先去重后校验”缺陷,并补回归测试锁定“首条无效、后条有效”的同键场景
|
||||
- 接受并修复 generated descriptor 校验对 `MethodInfo` 使用 `ReferenceEquals` 的过严比较,改为按方法语义等价匹配
|
||||
- 当前尚未提交的收尾切片仅剩:
|
||||
- `GFramework.Cqrs.Benchmarks/Messaging/NotificationLifetimeBenchmarks.cs`
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsRegistrationServiceTests.cs`
|
||||
@ -46,7 +51,7 @@ CQRS 迁移与收敛。
|
||||
## 当前活跃事实
|
||||
|
||||
- 当前分支:`feat/cqrs-optimization`
|
||||
- 当前 PR:`PR #347`
|
||||
- 当前 PR:`PR #348`
|
||||
- 当前写面:
|
||||
- `GFramework.Cqrs.Benchmarks/README.md`
|
||||
- `GFramework.Cqrs.Benchmarks/Messaging/NotificationLifetimeBenchmarks.cs`
|
||||
@ -110,7 +115,7 @@ CQRS 迁移与收敛。
|
||||
## 下一推荐步骤
|
||||
|
||||
1. 先提交当前未提交的 `NotificationLifetime + registration fallback tests + CQRS/legacy docs` 收尾切片,回收工作树到干净状态。
|
||||
2. 再次运行 `$gframework-pr-review`,复核 `PR #347` latest-head open thread 是否已随着本轮多波 head 收敛。
|
||||
2. 再次运行 `$gframework-pr-review`,复核 `PR #348` latest-head open thread 是否已随着本轮多波 head 收敛。
|
||||
3. 若继续扩 benchmark,优先从 `GFramework.Cqrs.Benchmarks/README.md` 已明确列出的 gap 中选下一个单文件切片,而不是继续扩大 shared infra 改动面。
|
||||
|
||||
## 活跃文档
|
||||
|
||||
@ -7,6 +7,29 @@ SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
## 2026-05-11
|
||||
|
||||
### 阶段:PR #348 latest-head review 再收口(CQRS-REWRITE-RP-134)
|
||||
|
||||
- 重新执行 `$gframework-pr-review` 抓取当前分支 `feat/cqrs-optimization` 对应的 `PR #348`
|
||||
- 本轮 latest-head open AI thread 复核结论:
|
||||
- `NotificationLifetimeBenchmarks.HandlerLifetime` 补 `[GenerateEnumExtensions]` 仍判定为泛化误报
|
||||
- 仓库没有“产品/benchmark 枚举默认都启用该特性”的现行约定
|
||||
- benchmark 项目也未接入 `GFramework.Core.SourceGenerators.Abstractions`,不应为局部对照枚举平白扩大 generator 依赖面
|
||||
- `NotificationLifetimeBenchmarks` 的 `_scopedContainer` 释放缺口与公开 benchmark API 的 XML 契约缺口仍成立,接受修复
|
||||
- `CqrsHandlerRegistrar` 中 generated descriptor 的“先去重后校验”缺陷仍成立,接受修复并补测试
|
||||
- `CqrsHandlerRegistrar` 对 `MethodInfo` 使用 `ReferenceEquals` 的过严比较仍成立,接受修复
|
||||
- active tracking / trace 的当前 PR 锚点仍停留在 `PR #347`,接受同步到 `PR #348`
|
||||
- 本轮主线程实施:
|
||||
- `NotificationLifetimeBenchmarks`
|
||||
- `Cleanup()` 将 `_scopedContainer` 一并交给 `BenchmarkCleanupHelper.DisposeAll(...)`
|
||||
- 为公开 benchmark 方法与公开 handler 方法补齐缺失的 `<returns>` / `<param>` XML 契约
|
||||
- `CqrsHandlerRegistrar`
|
||||
- request / stream generated descriptor 预热路径改为“先 `TryValidate...`,后写入 `registeredKeys`”
|
||||
- descriptor 对齐判断从 `ReferenceEquals(resolvedDescriptor.InvokerMethod, ...)` 调整为 `resolvedDescriptor.InvokerMethod.Equals(...)`
|
||||
- `CqrsGeneratedRequestInvokerProviderTests`
|
||||
- 新增 request / stream 两个回归用例,锁定“首条同键 descriptor 无效、后条有效时,仍应接受后条有效 generated descriptor”
|
||||
- `ai-plan/public/cqrs-rewrite/**`
|
||||
- 将 active tracking / trace 的当前 PR 锚点同步到 `PR #348`
|
||||
|
||||
### 阶段:PR #347 latest-head review 收口(CQRS-REWRITE-RP-132)
|
||||
|
||||
- 使用 `$gframework-pr-review` 重新抓取当前分支 `feat/cqrs-optimization` 对应的 `PR #347`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user