mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-06 16:16:44 +08:00
test(cqrs): 补充 provider fallback 回归
- 新增 non-enumerating request 与 stream provider 回归,锁定 dispatcher 会继续回退到反射路径 - 更新 CQRS 重写恢复点到 RP-074,并记录定向验证结果
This commit is contained in:
parent
1091594224
commit
8b36626266
@ -185,6 +185,46 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
|
||||
Assert.That(results, Is.EqualTo([300, 301]));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当 registry 只暴露 request invoker provider 接口、但不提供可枚举描述符契约时,
|
||||
/// dispatcher 仍会回退到既有反射路径,而不是错误依赖未预热的 generated metadata。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task SendAsync_Should_Fall_Back_To_Runtime_Path_When_Request_Provider_Does_Not_Enumerate_Descriptors()
|
||||
{
|
||||
var generatedAssembly = CreateGeneratedAssembly(
|
||||
typeof(NonEnumeratingRequestInvokerProviderRegistry),
|
||||
"GFramework.Cqrs.Tests.Cqrs.NonEnumeratingRequestInvokerAssembly, 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("runtime:payload"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当 registry 只暴露 stream invoker provider 接口、但不提供可枚举描述符契约时,
|
||||
/// dispatcher 仍会回退到既有流式反射路径。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task CreateStream_Should_Fall_Back_To_Runtime_Path_When_Stream_Provider_Does_Not_Enumerate_Descriptors()
|
||||
{
|
||||
var generatedAssembly = CreateGeneratedAssembly(
|
||||
typeof(NonEnumeratingStreamInvokerProviderRegistry),
|
||||
"GFramework.Cqrs.Tests.Cqrs.NonEnumeratingStreamInvokerAssembly, 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([3, 4]));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当 generated request invoker provider 返回实例方法时,
|
||||
/// dispatcher 会显式拒绝该描述符,而不是在后续绑定阶段静默接受非法合同。
|
||||
@ -497,6 +537,88 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟只暴露 request provider 接口、但不暴露描述符枚举契约的 generated registry。
|
||||
/// </summary>
|
||||
private sealed class NonEnumeratingRequestInvokerProviderRegistry :
|
||||
ICqrsHandlerRegistry,
|
||||
ICqrsRequestInvokerProvider
|
||||
{
|
||||
private static readonly CqrsRequestInvokerDescriptor Descriptor = 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)
|
||||
{
|
||||
if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string))
|
||||
{
|
||||
descriptor = Descriptor;
|
||||
return true;
|
||||
}
|
||||
|
||||
descriptor = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟只暴露 stream provider 接口、但不暴露描述符枚举契约的 generated registry。
|
||||
/// </summary>
|
||||
private sealed class NonEnumeratingStreamInvokerProviderRegistry :
|
||||
ICqrsHandlerRegistry,
|
||||
ICqrsStreamInvokerProvider
|
||||
{
|
||||
private static readonly CqrsStreamInvokerDescriptor Descriptor = 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)
|
||||
{
|
||||
if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int))
|
||||
{
|
||||
descriptor = Descriptor;
|
||||
return true;
|
||||
}
|
||||
|
||||
descriptor = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建带有 generated request invoker registry 元数据的程序集替身。
|
||||
/// </summary>
|
||||
|
||||
@ -7,7 +7,7 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-073`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-074`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前焦点:
|
||||
- 已完成一轮 `CQRS vs Mediator` 只读评估归档,结论已沉淀到 `archive/todos/cqrs-vs-mediator-assessment-rp063.md`
|
||||
@ -78,6 +78,9 @@ CQRS 迁移与收敛。
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs` 现新增 request / stream 两组 `non-static invoker` 与 `incompatible invoker` 回归,锁定 dispatcher 在首次绑定阶段会显式拒绝非法 generated descriptor
|
||||
- `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 现把 `Delegate.CreateDelegate(...)` 抛出的 `ArgumentException` 统一包装为已有 XML 文档承诺的 `InvalidOperationException`,保持 request / stream 两条错误消息语义一致
|
||||
- 本轮顺手为新增异步断言补齐 `ConfigureAwait(false)`,消除新测试引入的 `MA0004` warning
|
||||
- 已完成一轮 non-enumerating provider reflection fallback 回归:
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs` 现新增 request / stream 两条回归,锁定当 registry 只暴露 provider 接口、但不实现 `IEnumeratesCqrs*InvokerDescriptors` 时,registrar 不会预热 dispatcher 缓存,后续 dispatch 会继续回退到既有反射路径
|
||||
- 当前回归明确区分“provider 已注册”和“descriptor 已枚举入缓存”这两个阶段,避免后续把 `TryGetDescriptor(...)` 的存在误当成 dispatcher 会主动查询 provider 的合同
|
||||
- 当前相对 `origin/main` 的累计 branch diff 为 `24 files / 1754 changed lines`,仍低于本轮 `$gframework-batch-boot 50` 的主要 stop condition,可继续推进下一批低风险切片
|
||||
- 已将 mixed fallback 场景进一步收敛:当 runtime 允许同一程序集声明多个 `CqrsReflectionFallbackAttribute` 实例时,generator 现会把可直接引用的 fallback handlers 与仅能按名称恢复的 fallback handlers 拆分发射
|
||||
- `CqrsReflectionFallbackAttribute` 现允许多实例,以承载 `Type[]` 与字符串 fallback 元数据的组合输出
|
||||
@ -307,6 +310,12 @@ CQRS 迁移与收敛。
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.SendAsync_Should_Throw_When_Generated_Request_Invoker_Is_Not_Static|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.SendAsync_Should_Throw_When_Generated_Request_Invoker_Is_Incompatible|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.CreateStream_Should_Throw_When_Generated_Stream_Invoker_Is_Not_Static|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.CreateStream_Should_Throw_When_Generated_Stream_Invoker_Is_Incompatible|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.SendAsync_Should_Use_Generated_Request_Invoker_When_Provider_Is_Registered|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.CreateStream_Should_Use_Generated_Stream_Invoker_When_Provider_Is_Registered"`
|
||||
- 结果:通过
|
||||
- 备注:`6/6` passed;确认 request / stream 的非法 generated invoker 现统一抛出 `InvalidOperationException`,且原有 happy-path 未回归
|
||||
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
- 结果:通过
|
||||
- 备注:`0 warning / 0 error`;确认新增 non-enumerating provider 回归未引入构建告警
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests"`
|
||||
- 结果:通过
|
||||
- 备注:`14/14` passed;确认 request / stream 的 generated happy-path、异常路径与 non-enumerating provider 反射回退语义均保持通过
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
|
||||
- 结果:通过
|
||||
- 备注:`0 warning / 0 error`;确认 `CqrsRuntimeModule` 接线变更未引入 `GFramework.Core` 模块构建问题
|
||||
@ -340,6 +349,6 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 在保持 branch diff 明显低于 `50 files` 的前提下,继续挑选下一批低风险 `dispatch/invoker` 收敛切片,并优先考虑 request / stream provider 的剩余 runtime 失败边界或 generator gate 合同补强
|
||||
1. 在保持 branch diff 明显低于 `50 files` 的前提下,继续挑选下一批低风险 `dispatch/invoker` 收敛切片,并优先考虑 request / stream provider 的剩余 runtime 失败边界、缓存预热边界或 generator gate 合同补强
|
||||
2. 基于已落地的 notification publisher seam,评估是否需要第二阶段公开配置面、并行 publisher 或 telemetry decorator
|
||||
3. 单独规划旧 `Command` / `Query` API 的收口顺序;`LegacyICqrsRuntime` compatibility slice 已收口到显式 helper 与专门测试,可暂时移出最高优先级
|
||||
|
||||
@ -2,6 +2,27 @@
|
||||
|
||||
## 2026-04-30
|
||||
|
||||
### 阶段:non-enumerating provider reflection fallback 回归(CQRS-REWRITE-RP-074)
|
||||
|
||||
- 在 `RP-073` 提交后继续按 `gframework-batch-boot 50` 执行;当前 branch diff 相对 `origin/main` 仍远低于 `50 files` 阈值,因此继续追加一轮单文件 runtime contract 回归
|
||||
- 本轮接受只读 subagent 的收敛建议,把切片限定为“provider 已注册但未向 dispatcher 可枚举地贡献 descriptor”时的 fallback 语义
|
||||
- 主线程已完成:
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs` 新增 request / stream 两条回归,锁定仅实现 `ICqrsRequestInvokerProvider` / `ICqrsStreamInvokerProvider`、但未实现 `IEnumeratesCqrs*InvokerDescriptors` 的 registry 仍会让 dispatch 回退到既有反射路径
|
||||
- 当前回归刻意不修改 `CqrsDispatcher` 或 `CqrsHandlerRegistrar`:它只把现有实现和注释里已经隐含的“descriptor cache 预热优先于 provider 显式查询”语义提升为可执行合同
|
||||
|
||||
### 验证(RP-074)
|
||||
|
||||
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests"`
|
||||
- 结果:通过,`14/14` passed
|
||||
|
||||
### 当前下一步(RP-074)
|
||||
|
||||
1. 先提交本轮 non-enumerating provider 回归与恢复点更新
|
||||
2. 重新复算 branch diff 后,再判断是否继续推进 provider 的空枚举 descriptor 边界或在本轮阈值前停下
|
||||
3. 若继续下一批,优先保持单文件测试写集,不扩散到新的模块
|
||||
|
||||
### 阶段:generated invoker provider runtime 失败边界修复(CQRS-REWRITE-RP-073)
|
||||
|
||||
- 在 `RP-072` 提交后继续按 `gframework-batch-boot 50` 执行;当前 branch diff 相对 `origin/main` 仍为 `24 files`,文件阈值 headroom 依然充足,因此继续推进下一批 runtime 失败边界回归
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user