mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
test(cqrs): 锁定 precise reflected provider 边界
- 新增 request 与 stream generator 回归,明确 precise reflected 注册不会发射 invoker provider 元数据 - 更新 CQRS 重写恢复点到 RP-071,并记录本轮验证与边界结论
This commit is contained in:
parent
6b5c5d9e2d
commit
dc21188c79
@ -2105,6 +2105,219 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
}
|
||||
""";
|
||||
|
||||
private const string PreciseReflectedRequestInvokerProviderBoundarySource = """
|
||||
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 class Container
|
||||
{
|
||||
private sealed record HiddenResponse();
|
||||
|
||||
private sealed record HiddenRequest() : IRequest<HiddenResponse[]>;
|
||||
|
||||
private sealed class HiddenHandler : IRequestHandler<HiddenRequest, HiddenResponse[]>
|
||||
{
|
||||
public ValueTask<HiddenResponse[]> Handle(HiddenRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return ValueTask.FromResult(Array.Empty<HiddenResponse>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string PreciseReflectedStreamInvokerProviderBoundarySource = """
|
||||
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 class Container
|
||||
{
|
||||
private sealed record HiddenResponse();
|
||||
|
||||
private sealed record HiddenStream() : IStreamRequest<HiddenResponse[]>;
|
||||
|
||||
private sealed class HiddenHandler : IStreamRequestHandler<HiddenStream, HiddenResponse[]>
|
||||
{
|
||||
public async IAsyncEnumerable<HiddenResponse[]> Handle(HiddenStream request, CancellationToken cancellationToken)
|
||||
{
|
||||
yield return Array.Empty<HiddenResponse>();
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// 验证生成器会为当前程序集中的 request、notification 和 stream 处理器生成稳定顺序的注册器。
|
||||
/// </summary>
|
||||
@ -2732,6 +2945,28 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当 request handler 仍需走 precise reflected 注册时,
|
||||
/// 生成器即使检测到 request invoker provider runtime 合同,也不会错误发射无法稳定表达隐藏请求/响应类型的 provider 元数据。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Does_Not_Emit_Request_Invoker_Provider_Metadata_For_Precise_Reflected_Request_Registrations()
|
||||
{
|
||||
var generatedSource = RunGenerator(PreciseReflectedRequestInvokerProviderBoundarySource);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(
|
||||
generatedSource,
|
||||
Does.Contain("typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType("));
|
||||
Assert.That(generatedSource, Does.Contain(".MakeArrayType()"));
|
||||
Assert.That(generatedSource, Does.Not.Contain("ICqrsRequestInvokerProvider"));
|
||||
Assert.That(generatedSource, Does.Not.Contain("IEnumeratesCqrsRequestInvokerDescriptors"));
|
||||
Assert.That(generatedSource, Does.Not.Contain("CqrsRequestInvokerDescriptorEntry("));
|
||||
Assert.That(generatedSource, Does.Not.Contain("InvokeRequestHandler0"));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当 runtime 暴露 stream invoker provider 契约时,生成器会让 generated registry 同时发射
|
||||
/// stream invoker 描述符与对应的开放静态 invoker 方法。
|
||||
@ -2821,6 +3056,28 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当 stream handler 仍需走 precise reflected 注册时,
|
||||
/// 生成器即使检测到 stream invoker provider runtime 合同,也不会错误发射无法稳定表达隐藏请求/响应类型的 provider 元数据。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Does_Not_Emit_Stream_Invoker_Provider_Metadata_For_Precise_Reflected_Stream_Registrations()
|
||||
{
|
||||
var generatedSource = RunGenerator(PreciseReflectedStreamInvokerProviderBoundarySource);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(
|
||||
generatedSource,
|
||||
Does.Contain("typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler<,>).MakeGenericType("));
|
||||
Assert.That(generatedSource, Does.Contain(".MakeArrayType()"));
|
||||
Assert.That(generatedSource, Does.Not.Contain("ICqrsStreamInvokerProvider"));
|
||||
Assert.That(generatedSource, Does.Not.Contain("IEnumeratesCqrsStreamInvokerDescriptors"));
|
||||
Assert.That(generatedSource, Does.Not.Contain("CqrsStreamInvokerDescriptorEntry("));
|
||||
Assert.That(generatedSource, Does.Not.Contain("InvokeStreamHandler0"));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证日志字符串转义会覆盖换行、反斜杠和双引号,避免生成代码中的字符串字面量被意外截断。
|
||||
/// </summary>
|
||||
|
||||
@ -7,7 +7,7 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-070`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-071`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前焦点:
|
||||
- 已完成一轮 `CQRS vs Mediator` 只读评估归档,结论已沉淀到 `archive/todos/cqrs-vs-mediator-assessment-rp063.md`
|
||||
@ -68,6 +68,9 @@ CQRS 迁移与收敛。
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs` 现覆盖“实现类型隐藏、但 handler interface 可见”场景下的 generated request / stream invoker 消费路径
|
||||
- `HiddenImplementationGeneratedRequestInvokerProviderRegistry`、`HiddenImplementationGeneratedStreamInvokerProviderRegistry` 与对应 container / handler fixture 现锁定 registrar 接线后,dispatcher 会优先命中 generated descriptor,而不是退回反射 invoker
|
||||
- 当前 runtime 回归继续保持 `PreciseReflectedRegistrationSpec` 排除边界不变,只验证已允许发射 provider 元数据的 visible-interface hidden-implementation 场景
|
||||
- 已完成一轮 precise reflected invoker provider 合同边界回归:
|
||||
- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 现新增 request / stream 两条回归,明确当 handler 仍需走 `PreciseReflectedRegistrationSpec` 时,generator 即使检测到 invoker provider runtime 合同,也不会错误发射 descriptor、枚举接口或静态 invoker 桥接
|
||||
- 本轮接受了一条只读 subagent 的“继续评估 precise reflected + provider 发射”候选思路,但主线程复核后确认该候选并不存在可安全放宽的 `typeof(request/response)` 子集,因此收敛为“锁定当前排除边界”的测试批次,而不是修改生产 generator 逻辑
|
||||
- 当前相对 `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 元数据的组合输出
|
||||
@ -276,6 +279,15 @@ CQRS 迁移与收敛。
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests"`
|
||||
- 结果:通过
|
||||
- 备注:`8/8` passed;补齐 hidden implementation + visible interface 场景后,确认 generated request / stream invoker 在 runtime 侧也会优先命中 provider descriptor
|
||||
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
|
||||
- 结果:通过
|
||||
- 备注:`0 warning / 0 error`;确认本轮 precise reflected invoker provider 合同回归未引入 generator 编译告警
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release`
|
||||
- 结果:通过
|
||||
- 备注:`0 warning / 0 error`;并行验证时曾出现过 `MSB3026` 输出文件竞争噪音,随后已串行重跑并得到干净构建结果
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_For_Precise_Reflected_Request_Registrations|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_For_Precise_Reflected_Stream_Registrations|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Handler_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Handler_Interface"`
|
||||
- 结果:通过
|
||||
- 备注:`4/4` passed;串行确认 visible-interface hidden-implementation 仍发射 provider 元数据,而 precise reflected 注册继续保持“不发射 provider descriptor”的当前合同
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
|
||||
- 结果:通过
|
||||
- 备注:`0 warning / 0 error`;确认 `CqrsRuntimeModule` 接线变更未引入 `GFramework.Core` 模块构建问题
|
||||
@ -309,6 +321,6 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 在保持 branch diff 明显低于 `50 files` 的前提下,继续挑选下一批低风险 `dispatch/invoker` 收敛切片,并优先考虑 request / stream provider 的诊断、入口或测试补强
|
||||
1. 在保持 branch diff 明显低于 `50 files` 的前提下,继续挑选下一批低风险 `dispatch/invoker` 收敛切片,并优先考虑 request / stream provider 的诊断、入口或 runtime / generator 合同测试补强
|
||||
2. 基于已落地的 notification publisher seam,评估是否需要第二阶段公开配置面、并行 publisher 或 telemetry decorator
|
||||
3. 单独规划旧 `Command` / `Query` API 的收口顺序;`LegacyICqrsRuntime` compatibility slice 已收口到显式 helper 与专门测试,可暂时移出最高优先级
|
||||
|
||||
@ -2,6 +2,39 @@
|
||||
|
||||
## 2026-04-30
|
||||
|
||||
### 阶段:precise reflected invoker provider 合同边界回归(CQRS-REWRITE-RP-071)
|
||||
|
||||
- 在 `RP-070` 提交后继续按 `gframework-batch-boot 50` 执行;当前已提交 branch diff 仍为 `24 files`,headroom 充足,因此继续下一批 generator-only 合同收敛
|
||||
- 本轮先接受一条只读 subagent 的候选建议,评估是否可把 `PreciseReflectedRegistrationSpec` 的某个安全子集也纳入 request / stream provider 发射
|
||||
- 主线程复核 `TryCreatePreciseReflectedRegistration(...)`、`CreateRequestInvokerEmissions(...)` / `CreateStreamInvokerEmissions(...)` 与现有 precise 测试素材后确认:
|
||||
- precise reflected 分支之所以存在,正是因为 handler interface 的请求或响应类型无法完全通过 `typeof(...)` 稳定表达
|
||||
- 当前 provider descriptor 合同需要直接发射 `typeof(requestType)` / `typeof(responseType)`;因此不存在可无条件放宽的“安全子集”
|
||||
- 本轮最终不改生产 generator,而是把这条边界显式固化到回归测试,避免后续误把不存在的子集当成已支持能力
|
||||
- 主线程已完成:
|
||||
- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 新增两条回归,分别锁定 request / stream 的 precise reflected 注册不会发射 invoker provider 元数据
|
||||
- 同一组定向测试同时复核 hidden-implementation + visible-interface 场景仍会继续发射 provider 元数据,确保“允许发射”和“继续排除”的边界没有被本轮测试收紧弄混
|
||||
|
||||
### 验证(RP-071)
|
||||
|
||||
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- 备注:并行验证时曾出现 `MSB3026` 输出文件竞争噪音,随后已串行重跑同批命令并取得干净结果
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_For_Precise_Reflected_Request_Registrations|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_For_Precise_Reflected_Stream_Registrations|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Handler_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Handler_Interface"`
|
||||
- 结果:通过,`4/4` passed
|
||||
- `git diff --name-only origin/main...HEAD | wc -l`
|
||||
- 结果:通过
|
||||
- 备注:当前相对 `origin/main` 的已提交 branch diff 仍为 `24 files`
|
||||
- `git diff --numstat origin/main...HEAD`
|
||||
- 结果:通过
|
||||
- 备注:当前相对 `origin/main` 的工作分支累计 diff 为 `1793 changed lines`
|
||||
|
||||
### 当前下一步(RP-071)
|
||||
|
||||
1. 先提交本轮 generator 合同边界回归,保持恢复点、trace 与已验证测试状态一致
|
||||
2. 继续挑选下一批低风险切片,优先考虑 request / stream provider 的 runtime 或 generator 诊断边界,而不是贸然扩大 precise reflected 支持面
|
||||
3. 若下一批仍可拆分为非冲突文件,再恢复只读 / 写入 subagent 的分工方式压低主线程上下文
|
||||
### 阶段:hidden-implementation generated invoker runtime 回归补强(CQRS-REWRITE-RP-070)
|
||||
|
||||
- 在 `5a77e2fb` 提交后补齐 active `ai-plan` 恢复入口,继续按 `gframework-batch-boot 50` 执行,基线仍为当前本地 `origin/main`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user