feat(cqrs): 扩大生成式 invoker 发射范围

- 扩大 request 与 stream invoker 发射范围到 reflected-implementation 注册场景

- 补充 hidden implementation 回归测试并更新 CQRS ai-plan 恢复点
This commit is contained in:
gewuyou 2026-04-30 14:07:05 +08:00
parent 172c08176c
commit eb30388267
5 changed files with 76 additions and 7 deletions

View File

@ -23,7 +23,9 @@ public sealed partial class CqrsHandlerRegistryGenerator
private readonly record struct ReflectedImplementationRegistrationSpec(
string HandlerInterfaceDisplayName,
string HandlerInterfaceLogName);
string HandlerInterfaceLogName,
RequestInvokerRegistrationSpec? RequestInvokerRegistration,
StreamInvokerRegistrationSpec? StreamInvokerRegistration);
private readonly record struct OrderedRegistrationSpec(
string HandlerInterfaceLogName,

View File

@ -73,7 +73,7 @@ public sealed partial class CqrsHandlerRegistryGenerator
}
/// <summary>
/// 从 direct handler 注册描述中提取 request invoker 发射计划。
/// 从可直接表达 handler 接口的注册描述中提取 request invoker 发射计划。
/// </summary>
/// <param name="supportsRequestInvokerProvider">
/// 指示当前 runtime 是否同时暴露 <c>ICqrsRequestInvokerProvider</c> 与
@ -81,7 +81,8 @@ public sealed partial class CqrsHandlerRegistryGenerator
/// </param>
/// <param name="registrations">已按稳定顺序整理完成的 handler 注册描述。</param>
/// <returns>
/// 由 <c>directRegistration.RequestInvokerRegistration</c> 派生出的 <see cref="RequestInvokerEmissionSpec" /> 集合。
/// 由 direct registration 或 reflected-implementation registration 上的
/// <c>RequestInvokerRegistration</c> 派生出的 <see cref="RequestInvokerEmissionSpec" /> 集合。
/// <c>methodIndex</c> 按 <paramref name="registrations" /> 与其 direct registration 的遍历顺序单调递增,
/// 因而只要上游排序稳定,生成的 invoker 方法名与描述符顺序就跨运行保持稳定。
/// </returns>
@ -111,13 +112,25 @@ public sealed partial class CqrsHandlerRegistryGenerator
directRegistration.HandlerInterfaceDisplayName,
methodIndex++));
}
foreach (var reflectedRegistration in registration.ReflectedImplementationRegistrations)
{
if (reflectedRegistration.RequestInvokerRegistration is not { } requestInvokerRegistration)
continue;
builder.Add(new RequestInvokerEmissionSpec(
requestInvokerRegistration.RequestTypeDisplayName,
requestInvokerRegistration.ResponseTypeDisplayName,
reflectedRegistration.HandlerInterfaceDisplayName,
methodIndex++));
}
}
return builder.ToImmutable();
}
/// <summary>
/// 从 direct handler 注册描述中提取 stream invoker 发射计划。
/// 从可直接表达 handler 接口的注册描述中提取 stream invoker 发射计划。
/// </summary>
/// <param name="supportsStreamInvokerProvider">
/// 指示当前 runtime 是否同时暴露 <c>ICqrsStreamInvokerProvider</c> 与
@ -125,7 +138,8 @@ public sealed partial class CqrsHandlerRegistryGenerator
/// </param>
/// <param name="registrations">已按稳定顺序整理完成的 handler 注册描述。</param>
/// <returns>
/// 由 <c>directRegistration.StreamInvokerRegistration</c> 派生出的 <see cref="StreamInvokerEmissionSpec" /> 集合。
/// 由 direct registration 或 reflected-implementation registration 上的
/// <c>StreamInvokerRegistration</c> 派生出的 <see cref="StreamInvokerEmissionSpec" /> 集合。
/// <c>methodIndex</c> 按 <paramref name="registrations" /> 与其 direct registration 的遍历顺序单调递增,
/// 因而只要上游排序稳定,生成的 invoker 方法名与描述符顺序就跨运行保持稳定。
/// </returns>
@ -151,6 +165,18 @@ public sealed partial class CqrsHandlerRegistryGenerator
directRegistration.HandlerInterfaceDisplayName,
methodIndex++));
}
foreach (var reflectedRegistration in registration.ReflectedImplementationRegistrations)
{
if (reflectedRegistration.StreamInvokerRegistration is not { } streamInvokerRegistration)
continue;
builder.Add(new StreamInvokerEmissionSpec(
streamInvokerRegistration.RequestTypeDisplayName,
streamInvokerRegistration.ResponseTypeDisplayName,
reflectedRegistration.HandlerInterfaceDisplayName,
methodIndex++));
}
}
return builder.ToImmutable();

View File

@ -258,7 +258,13 @@ public sealed partial class CqrsHandlerRegistryGenerator : IIncrementalGenerator
{
reflectedImplementationRegistrations.Add(new ReflectedImplementationRegistrationSpec(
handlerInterface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
GetLogDisplayName(handlerInterface)));
GetLogDisplayName(handlerInterface),
TryCreateRequestInvokerRegistrationSpec(handlerInterface, out var requestInvokerRegistration)
? requestInvokerRegistration
: null,
TryCreateStreamInvokerRegistrationSpec(handlerInterface, out var streamInvokerRegistration)
? streamInvokerRegistration
: null));
return true;
}

View File

@ -7,7 +7,7 @@ CQRS 迁移与收敛。
## 当前恢复点
- 恢复点编号:`CQRS-REWRITE-RP-068`
- 恢复点编号:`CQRS-REWRITE-RP-069`
- 当前阶段:`Phase 8`
- 当前焦点:
- 已完成一轮 `CQRS vs Mediator` 只读评估归档,结论已沉淀到 `archive/todos/cqrs-vs-mediator-assessment-rp063.md`
@ -60,6 +60,10 @@ CQRS 迁移与收敛。
- `GFramework.SourceGenerators.Tests` 已补充 generator 回归,锁定当 runtime 暴露新契约时generated registry 会额外发射 stream invoker provider 成员与 invoker 方法
- `GFramework.Cqrs/README.md``GFramework.Cqrs.SourceGenerators/README.md``docs/zh-CN/core/cqrs.md`
`docs/zh-CN/source-generators/cqrs-handler-registry-generator.md` 现已同步说明 generated stream invoker 的接线与回退边界
- 已完成一轮 generated invoker 发射范围补强:
- `CqrsHandlerRegistryGenerator` 现会把 generated request / stream invoker 的发射范围,从“仅 direct registration”扩大到“实现类型隐藏、但 handler interface 仍可直接表达”的 reflected-implementation registration
- 当前扩展仍刻意避开 `PreciseReflectedRegistrationSpec`,不把隐藏 request/response 类型误拉进 provider 发射,继续保持生成源码可编译边界
- `GFramework.SourceGenerators.Tests` 已新增两条 hidden-implementation 回归,锁定 request / stream provider 在该场景下都会继续发射 descriptor 与静态 invoker 方法
- 已将 mixed fallback 场景进一步收敛:当 runtime 允许同一程序集声明多个 `CqrsReflectionFallbackAttribute` 实例时generator 现会把可直接引用的 fallback handlers 与仅能按名称恢复的 fallback handlers 拆分发射
- `CqrsReflectionFallbackAttribute` 现允许多实例,以承载 `Type[]` 与字符串 fallback 元数据的组合输出
- 已将 generator 的程序集级 fallback 元数据进一步收敛:当全部 fallback handlers 都可直接引用且 runtime 暴露 `params Type[]` 合同时,生成器现优先发射 `typeof(...)` 形式的 fallback 元数据
@ -261,6 +265,9 @@ CQRS 迁移与收敛。
- `GIT_DIR=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework/.git/worktrees/GFramework-cqrs GIT_WORK_TREE=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework-WorkTree/GFramework-cqrs bash scripts/validate-csharp-naming.sh`
- 结果:通过
- 备注:`1059` 个 tracked C# 文件命名校验全部通过;本轮新增 stream invoker 类型与测试命名未引入回归
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
- 结果:通过
- 备注:`4/4` passed确认 hidden implementation + visible interface 场景也会继续发射 request / stream invoker provider 元数据
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
- 结果:通过
- 备注:`0 warning / 0 error`;确认 `CqrsRuntimeModule` 接线变更未引入 `GFramework.Core` 模块构建问题

View File

@ -58,6 +58,34 @@
2. 优先候选仍是 notification 路径是否值得引入同类 generated invoker seam或继续补强 request / stream provider 的公开 API 入口与诊断语义
3. 下一批落地前先提交当前 stream provider 批次,避免未提交改动持续堆叠
### 阶段generated invoker reflected-implementation 发射范围补强CQRS-REWRITE-RP-069
- 在 `RP-068` 提交后,重新复算 branch diff相对 `origin/main` 升至 `20 files / 1015 changed lines`,仍明显低于 `gframework-batch-boot 50` 的 stop condition因此继续下一批
- 本轮目标只收敛 source generator不扩散到 runtime 或公开文档:把 generated request / stream invoker 的发射范围从“仅 direct registration”扩大到“实现类型隐藏、但 handler interface 可直接表达”的 reflected-implementation registration
- 接受只读 subagent 结论后确认:
- 现有分类阶段已经为 reflected-implementation registration 保留了 request / stream invoker registration 元数据
- 真正缺口只在 `CreateRequestInvokerEmissions(...)``CreateStreamInvokerEmissions(...)` 仍只遍历 `DirectRegistrations`
- `PreciseReflectedRegistrationSpec` 继续排除在 provider 发射范围外,避免隐藏 request/response 类型导致生成源码不可编译
- 主线程已完成:
- `ReflectedImplementationRegistrationSpec` 显式承载 request / stream invoker registration 元数据
- `CreateRequestInvokerEmissions(...)``CreateStreamInvokerEmissions(...)` 现会同时消费 reflected-implementation registration
- `GFramework.SourceGenerators.Tests` 已新增 hidden-implementation + visible-interface 两条 provider 回归
- 本轮不改 runtimedispatcher / registrar 对 generated provider 的消费语义保持不变,变化只在 generator 愿意发射更多可安全静态表达的 descriptor
### 验证RP-069
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Generates_Direct_Interface_Registrations_For_Hidden_Implementation_When_Handler_Interface_Is_Public|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
- 结果:通过,`3/3` passed
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
- 结果:通过,`4/4` passed
### 当前下一步RP-069
1. 提交当前 generator-only 批次,继续保持每个低风险切片可独立回滚与审查
2. 继续评估下一个能明显降低反射占比、但不需要同时改动 runtime 语义的切片
### 阶段generated request invoker provider 最小落地CQRS-REWRITE-RP-067
- 继续按 `gframework-batch-boot 50` 执行,基线仍为本地现有 `origin/main`