mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-09 18:38:59 +08:00
fix(cqrs): 拆分处理器注册长方法
- 优化 CqrsHandlerRegistrar 的 generated registry 激活与 fallback 解析拆分 - 保持原有日志文本、缓存策略与 reflection fallback 语义不变 - 更新 analyzer warning reduction 的 active tracking 与 trace,记录 RP-002 验证结果
This commit is contained in:
parent
5175f00178
commit
5c7870ca3e
@ -88,63 +88,14 @@ internal static class CqrsHandlerRegistrar
|
||||
if (registryTypes.Count == 0)
|
||||
return GeneratedRegistrationResult.NoGeneratedRegistry();
|
||||
|
||||
var registries = new List<ICqrsHandlerRegistry>(registryTypes.Count);
|
||||
foreach (var registryType in registryTypes)
|
||||
{
|
||||
var activationMetadata = RegistryActivationMetadataCache.GetOrAdd(
|
||||
registryType,
|
||||
AnalyzeRegistryActivation);
|
||||
if (!TryCreateGeneratedRegistries(registryTypes, assemblyName, logger, out var registries))
|
||||
return GeneratedRegistrationResult.NoGeneratedRegistry();
|
||||
|
||||
if (!activationMetadata.ImplementsRegistryContract)
|
||||
{
|
||||
logger.Warn(
|
||||
$"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it does not implement {typeof(ICqrsHandlerRegistry).FullName}.");
|
||||
return GeneratedRegistrationResult.NoGeneratedRegistry();
|
||||
}
|
||||
|
||||
if (activationMetadata.IsAbstract)
|
||||
{
|
||||
logger.Warn(
|
||||
$"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it is abstract.");
|
||||
return GeneratedRegistrationResult.NoGeneratedRegistry();
|
||||
}
|
||||
|
||||
if (activationMetadata.Factory is null)
|
||||
{
|
||||
logger.Warn(
|
||||
$"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it does not expose an accessible parameterless constructor.");
|
||||
return GeneratedRegistrationResult.NoGeneratedRegistry();
|
||||
}
|
||||
|
||||
var registry = activationMetadata.Factory();
|
||||
registries.Add(registry);
|
||||
}
|
||||
|
||||
foreach (var registry in registries)
|
||||
{
|
||||
logger.Debug(
|
||||
$"Registering CQRS handlers for assembly {assemblyName} via generated registry {registry.GetType().FullName}.");
|
||||
registry.Register(services, logger);
|
||||
}
|
||||
|
||||
var reflectionFallbackMetadata = assemblyMetadata.ReflectionFallbackMetadata;
|
||||
if (reflectionFallbackMetadata is not null)
|
||||
{
|
||||
if (reflectionFallbackMetadata.HasExplicitTypes)
|
||||
{
|
||||
logger.Debug(
|
||||
$"Generated CQRS registry for assembly {assemblyName} requested targeted reflection fallback for {reflectionFallbackMetadata.Types.Count} unsupported handler type(s).");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug(
|
||||
$"Generated CQRS registry for assembly {assemblyName} requested full reflection fallback for unsupported handlers.");
|
||||
}
|
||||
|
||||
return GeneratedRegistrationResult.WithReflectionFallback(reflectionFallbackMetadata);
|
||||
}
|
||||
|
||||
return GeneratedRegistrationResult.FullyHandled();
|
||||
RegisterGeneratedRegistries(services, registries, assemblyName, logger);
|
||||
return BuildGeneratedRegistrationResult(
|
||||
assemblyMetadata.ReflectionFallbackMetadata,
|
||||
assemblyName,
|
||||
logger);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@ -186,12 +137,138 @@ internal static class CqrsHandlerRegistrar
|
||||
// Request/notification handlers receive context injection before every dispatch.
|
||||
// Transient registration avoids sharing mutable Context across concurrent requests.
|
||||
services.AddTransient(handlerInterface, implementationType);
|
||||
logger.Debug(
|
||||
logger.Debug(
|
||||
$"Registered CQRS handler {implementationType.FullName} as {handlerInterface.FullName}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 激活当前程序集声明的所有 generated registry;若任一 registry 不满足运行时契约,则整批回退到反射扫描。
|
||||
/// </summary>
|
||||
/// <param name="registryTypes">程序集声明的 generated registry 类型列表。</param>
|
||||
/// <param name="assemblyName">用于诊断的程序集稳定名称。</param>
|
||||
/// <param name="logger">日志记录器。</param>
|
||||
/// <param name="registries">成功激活后的 registry 实例。</param>
|
||||
/// <returns>当全部 registry 都可安全激活时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
|
||||
private static bool TryCreateGeneratedRegistries(
|
||||
IReadOnlyList<Type> registryTypes,
|
||||
string assemblyName,
|
||||
ILogger logger,
|
||||
out IReadOnlyList<ICqrsHandlerRegistry> registries)
|
||||
{
|
||||
var activatedRegistries = new List<ICqrsHandlerRegistry>(registryTypes.Count);
|
||||
foreach (var registryType in registryTypes)
|
||||
{
|
||||
if (!TryCreateGeneratedRegistry(registryType, assemblyName, logger, out var registry))
|
||||
{
|
||||
registries = Array.Empty<ICqrsHandlerRegistry>();
|
||||
return false;
|
||||
}
|
||||
|
||||
activatedRegistries.Add(registry);
|
||||
}
|
||||
|
||||
registries = activatedRegistries;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 激活单个 generated registry,并在契约不满足时输出与原先完全一致的回退诊断。
|
||||
/// </summary>
|
||||
/// <param name="registryType">要分析的 generated registry 类型。</param>
|
||||
/// <param name="assemblyName">当前程序集的稳定名称。</param>
|
||||
/// <param name="logger">日志记录器。</param>
|
||||
/// <param name="registry">激活成功后的 registry 实例。</param>
|
||||
/// <returns>当 registry 可安全使用时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
|
||||
private static bool TryCreateGeneratedRegistry(
|
||||
Type registryType,
|
||||
string assemblyName,
|
||||
ILogger logger,
|
||||
out ICqrsHandlerRegistry registry)
|
||||
{
|
||||
var activationMetadata = RegistryActivationMetadataCache.GetOrAdd(
|
||||
registryType,
|
||||
AnalyzeRegistryActivation);
|
||||
|
||||
if (!activationMetadata.ImplementsRegistryContract)
|
||||
{
|
||||
logger.Warn(
|
||||
$"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it does not implement {typeof(ICqrsHandlerRegistry).FullName}.");
|
||||
registry = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (activationMetadata.IsAbstract)
|
||||
{
|
||||
logger.Warn(
|
||||
$"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it is abstract.");
|
||||
registry = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (activationMetadata.Factory is null)
|
||||
{
|
||||
logger.Warn(
|
||||
$"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it does not expose an accessible parameterless constructor.");
|
||||
registry = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
registry = activationMetadata.Factory();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用所有已激活的 generated registry 完成 CQRS handler 注册,并保留稳定的调试日志顺序。
|
||||
/// </summary>
|
||||
/// <param name="services">目标服务集合。</param>
|
||||
/// <param name="registries">已通过契约校验的 registry 实例。</param>
|
||||
/// <param name="assemblyName">当前程序集的稳定名称。</param>
|
||||
/// <param name="logger">日志记录器。</param>
|
||||
private static void RegisterGeneratedRegistries(
|
||||
IServiceCollection services,
|
||||
IReadOnlyList<ICqrsHandlerRegistry> registries,
|
||||
string assemblyName,
|
||||
ILogger logger)
|
||||
{
|
||||
foreach (var registry in registries)
|
||||
{
|
||||
logger.Debug(
|
||||
$"Registering CQRS handlers for assembly {assemblyName} via generated registry {registry.GetType().FullName}.");
|
||||
registry.Register(services, logger);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 generated registry 的 fallback 元数据转换为统一的注册结果,并记录下一阶段是定向补扫还是整程序集扫描。
|
||||
/// </summary>
|
||||
/// <param name="reflectionFallbackMetadata">生成注册器声明的反射补扫元数据。</param>
|
||||
/// <param name="assemblyName">当前程序集的稳定名称。</param>
|
||||
/// <param name="logger">日志记录器。</param>
|
||||
/// <returns>描述 generated registry 是否已完全处理当前程序集的结果对象。</returns>
|
||||
private static GeneratedRegistrationResult BuildGeneratedRegistrationResult(
|
||||
ReflectionFallbackMetadata? reflectionFallbackMetadata,
|
||||
string assemblyName,
|
||||
ILogger logger)
|
||||
{
|
||||
if (reflectionFallbackMetadata is null)
|
||||
return GeneratedRegistrationResult.FullyHandled();
|
||||
|
||||
if (reflectionFallbackMetadata.HasExplicitTypes)
|
||||
{
|
||||
logger.Debug(
|
||||
$"Generated CQRS registry for assembly {assemblyName} requested targeted reflection fallback for {reflectionFallbackMetadata.Types.Count} unsupported handler type(s).");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug(
|
||||
$"Generated CQRS registry for assembly {assemblyName} requested full reflection fallback for unsupported handlers.");
|
||||
}
|
||||
|
||||
return GeneratedRegistrationResult.WithReflectionFallback(reflectionFallbackMetadata);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定实现类型上所有受支持的 CQRS handler 接口,并缓存筛选与排序结果。
|
||||
/// </summary>
|
||||
@ -255,6 +332,29 @@ internal static class CqrsHandlerRegistrar
|
||||
return null;
|
||||
|
||||
var resolvedTypes = new List<Type>();
|
||||
AppendDirectFallbackTypes(fallbackAttributes, resolvedTypes, assemblyName, logger);
|
||||
AppendNamedFallbackTypes(assembly, fallbackAttributes, resolvedTypes, assemblyName, logger);
|
||||
|
||||
return new ReflectionFallbackMetadata(
|
||||
resolvedTypes
|
||||
.Distinct()
|
||||
.OrderBy(GetTypeSortKey, StringComparer.Ordinal)
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 追加 attribute 里直接携带的 fallback 类型,并过滤掉跨程序集误声明的条目。
|
||||
/// </summary>
|
||||
/// <param name="fallbackAttributes">当前程序集上的 fallback attribute 集合。</param>
|
||||
/// <param name="resolvedTypes">待补充的已解析类型集合。</param>
|
||||
/// <param name="assemblyName">当前程序集的稳定名称。</param>
|
||||
/// <param name="logger">日志记录器。</param>
|
||||
private static void AppendDirectFallbackTypes(
|
||||
IReadOnlyList<CqrsReflectionFallbackAttribute> fallbackAttributes,
|
||||
ICollection<Type> resolvedTypes,
|
||||
string assemblyName,
|
||||
ILogger logger)
|
||||
{
|
||||
foreach (var fallbackType in fallbackAttributes
|
||||
.SelectMany(static attribute => attribute.FallbackHandlerTypes)
|
||||
.Where(static type => type is not null)
|
||||
@ -273,37 +373,65 @@ internal static class CqrsHandlerRegistrar
|
||||
|
||||
resolvedTypes.Add(fallbackType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 追加 attribute 里以类型名声明的 fallback 条目,并保留逐项失败的诊断能力。
|
||||
/// </summary>
|
||||
/// <param name="assembly">当前待解析的程序集。</param>
|
||||
/// <param name="fallbackAttributes">当前程序集上的 fallback attribute 集合。</param>
|
||||
/// <param name="resolvedTypes">待补充的已解析类型集合。</param>
|
||||
/// <param name="assemblyName">当前程序集的稳定名称。</param>
|
||||
/// <param name="logger">日志记录器。</param>
|
||||
private static void AppendNamedFallbackTypes(
|
||||
Assembly assembly,
|
||||
IReadOnlyList<CqrsReflectionFallbackAttribute> fallbackAttributes,
|
||||
ICollection<Type> resolvedTypes,
|
||||
string assemblyName,
|
||||
ILogger logger)
|
||||
{
|
||||
foreach (var typeName in fallbackAttributes
|
||||
.SelectMany(static attribute => attribute.FallbackHandlerTypeNames)
|
||||
.Where(static name => !string.IsNullOrWhiteSpace(name))
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.OrderBy(static name => name, StringComparer.Ordinal))
|
||||
{
|
||||
try
|
||||
{
|
||||
var type = assembly.GetType(typeName, throwOnError: false, ignoreCase: false);
|
||||
if (type is null)
|
||||
{
|
||||
logger.Warn(
|
||||
$"Generated CQRS reflection fallback type {typeName} could not be resolved in assembly {assemblyName}. Skipping targeted fallback entry.");
|
||||
continue;
|
||||
}
|
||||
TryAppendNamedFallbackType(assembly, resolvedTypes, assemblyName, typeName, logger);
|
||||
}
|
||||
}
|
||||
|
||||
resolvedTypes.Add(type);
|
||||
}
|
||||
catch (Exception exception)
|
||||
/// <summary>
|
||||
/// 解析并追加单个按名称声明的 fallback 类型,同时保留“找不到”与“加载异常”两类不同日志语义。
|
||||
/// </summary>
|
||||
/// <param name="assembly">当前待解析的程序集。</param>
|
||||
/// <param name="resolvedTypes">待补充的已解析类型集合。</param>
|
||||
/// <param name="assemblyName">当前程序集的稳定名称。</param>
|
||||
/// <param name="typeName">要解析的完整类型名。</param>
|
||||
/// <param name="logger">日志记录器。</param>
|
||||
private static void TryAppendNamedFallbackType(
|
||||
Assembly assembly,
|
||||
ICollection<Type> resolvedTypes,
|
||||
string assemblyName,
|
||||
string typeName,
|
||||
ILogger logger)
|
||||
{
|
||||
try
|
||||
{
|
||||
var type = assembly.GetType(typeName, throwOnError: false, ignoreCase: false);
|
||||
if (type is null)
|
||||
{
|
||||
logger.Warn(
|
||||
$"Generated CQRS reflection fallback type {typeName} failed to load in assembly {assemblyName}: {exception.Message}");
|
||||
$"Generated CQRS reflection fallback type {typeName} could not be resolved in assembly {assemblyName}. Skipping targeted fallback entry.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return new ReflectionFallbackMetadata(
|
||||
resolvedTypes
|
||||
.Distinct()
|
||||
.OrderBy(GetTypeSortKey, StringComparer.Ordinal)
|
||||
.ToArray());
|
||||
resolvedTypes.Add(type);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.Warn(
|
||||
$"Generated CQRS reflection fallback type {typeName} failed to load in assembly {assemblyName}: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -7,28 +7,30 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-001`
|
||||
- 当前阶段:`Phase 1`
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-002`
|
||||
- 当前阶段:`Phase 2`
|
||||
- 当前焦点:
|
||||
- 已将旧 `local-plan/` 迁入 `ai-plan/public/analyzer-warning-reduction/`,active 入口只保留当前恢复信息
|
||||
- 基于现有剩余热点,评估 `MA0051`、`MA0048`、`MA0046` 与少量 `MA0016` 是否适合继续在同一主线上处理
|
||||
- 若继续推进,优先选择不引入 API rename、公共契约漂移或 Godot 宿主不稳定测试的切入点
|
||||
- 已完成 `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 的 `MA0051` 长方法拆分,保持 generated registry、fallback 与缓存语义不变
|
||||
- 已确认 `GFramework.Cqrs` 定向构建恢复为 `0 Warning(s)`,该 warning slice 已不再是当前主题的阻塞项
|
||||
- 下一轮若继续推进,优先从 `GFramework.Core` 剩余的 `MA0051`、`MA0046` 或低风险 `MA0016` 中只选一个切入点
|
||||
|
||||
## 当前状态摘要
|
||||
|
||||
- 已完成 `GFramework.Core`、`GFramework.Cqrs`、`GFramework.Godot` 与部分 source generator 的低风险 warning 清理
|
||||
- 已完成多轮 CodeRabbit follow-up 修复,并用定向测试与项目/解决方案构建验证了关键回归风险
|
||||
- 当前剩余 warning 已集中到长方法、文件/类型命名冲突、delegate 形状和少量公共集合抽象接口问题
|
||||
- 当前 `GFramework.Cqrs` 的剩余 warning 热点已从 active 入口移除;主题内剩余 warning 主要集中在 `GFramework.Core` 长方法、
|
||||
文件/类型命名冲突、delegate 形状和少量公共集合抽象接口问题
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
- 当前主题仍是 active topic,因为剩余结构性 warning 是否继续推进尚未决策
|
||||
- `RP-001` 的详细实现历史、测试记录和 warning 热点清单已归档到主题内 `archive/`
|
||||
- `RP-002` 已在不改公共契约的前提下完成 `CqrsHandlerRegistrar` 结构拆分,并通过定向 build/test 验证
|
||||
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
|
||||
|
||||
## 当前风险
|
||||
|
||||
- 结构性重构风险:剩余 `MA0051` 与 `MA0048` 可能要求较大的文件拆分或类型重命名
|
||||
- 结构性重构风险:剩余 `GFramework.Core` 侧 `MA0051` 与 `MA0048` 可能要求较大的文件拆分或类型重命名
|
||||
- 缓解措施:只在下一轮明确接受结构调整成本时再继续推进,不在恢复点模糊的情况下顺手扩面
|
||||
- 测试宿主稳定性风险:部分 Godot 失败路径在当前 .NET 测试宿主下仍不稳定
|
||||
- 缓解措施:继续优先使用稳定的 targeted test、项目构建和相邻 smoke test 组合验证
|
||||
@ -43,10 +45,14 @@
|
||||
## 验证说明
|
||||
|
||||
- `RP-001` 的详细 warning 清理、回归修复与定向验证命令均已迁入主题内历史归档
|
||||
- `RP-002` 的定向验证结果:
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:UseSharedCompilation=false -p:RestoreFallbackFolders=`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter FullyQualifiedName~CqrsHandlerRegistrarTests -p:RestoreFallbackFolders=`
|
||||
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 若要继续该主题,先读 active tracking,再按需展开历史归档中的 warning 热点与验证记录
|
||||
2. 从 `MA0051`、`MA0048`、`MA0046` 中只选一个结构性切入点继续,不要在同一轮同时扩多个风险面
|
||||
2. 优先在 `GFramework.Core/Architectures/ArchitectureLifecycle.cs`、`GFramework.Core/Coroutine/CoroutineScheduler.cs` 与
|
||||
`GFramework.Core/Pause/PauseStackManager.cs` 的 `MA0051` 中只选一个继续,不要在同一轮同时扩多个风险面
|
||||
3. 若本主题确认暂缓,可保持当前归档状态,不需要再恢复 `local-plan/`
|
||||
|
||||
@ -1,5 +1,24 @@
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
## 2026-04-21
|
||||
|
||||
### 阶段:CQRS `MA0051` 收口(RP-002)
|
||||
|
||||
- 依据 active tracking 中“先只选一个结构性切入点”的约束,选定 `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs`
|
||||
作为低风险下一步,因为它已有稳定的 targeted test 覆盖 generated registry、reflection fallback、缓存和重复注册行为
|
||||
- 将 `TryRegisterGeneratedHandlers` 拆分为 registry 激活、批量注册和 fallback 结果构建三个辅助阶段,同时把
|
||||
`GetReflectionFallbackMetadata` 的直接类型解析与按名称解析拆开,降低长方法复杂度但不改日志文本与回退语义
|
||||
- 顺手修正 `RegisterAssemblyHandlers` 内部调试日志的缩进,未改注册顺序、生命周期或服务描述符写入逻辑
|
||||
- 验证通过:
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:UseSharedCompilation=false -p:RestoreFallbackFolders=`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter FullyQualifiedName~CqrsHandlerRegistrarTests -p:RestoreFallbackFolders=`
|
||||
- 结果:`11 Passed`,`0 Failed`
|
||||
- 新发现的环境注意事项:
|
||||
- 当前 WSL worktree 下若不显式传入 `-p:RestoreFallbackFolders=`,Linux `dotnet` 会读取不存在的 Windows fallback package
|
||||
folder 并导致 `ResolvePackageAssets` 失败
|
||||
- sandbox 内运行 `dotnet` 会因 MSBuild named-pipe 限制失败;需要在提权上下文中执行 .NET 验证
|
||||
|
||||
## 2026-04-19
|
||||
|
||||
### 阶段:local-plan 迁移收口(RP-001)
|
||||
@ -28,5 +47,5 @@
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 后续若继续 analyzer warning reduction,只从 `ai-plan/public/analyzer-warning-reduction/` 进入,不再恢复 `local-plan/`
|
||||
2. 若 active 入口再次积累多轮已完成且已验证阶段,继续按同一模式迁入该主题自己的 `archive/`
|
||||
1. 若继续 analyzer warning reduction,优先回到 `GFramework.Core` 剩余 `MA0051` 热点,并继续保持“单 warning family、单切入点”的节奏
|
||||
2. 后续所有 WSL 下的 .NET 定向验证命令继续显式附带 `-p:RestoreFallbackFolders=`,避免把环境问题误判成代码回归
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user