From 7209fdc32d4b0459a39b4d1cc9c560611358e413 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:38:52 +0800 Subject: [PATCH] =?UTF-8?q?docs(cqrs):=20=E6=94=B6=E5=8F=A3=E6=97=A7?= =?UTF-8?q?=E7=89=88=E8=BF=90=E8=A1=8C=E6=97=B6=E5=88=AB=E5=90=8D=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 LegacyICqrsRuntime 兼容层说明,明确旧命名空间别名与正式 CQRS runtime seam 的边界 - 补充容器基础设施回填 legacy alias 的回归测试,并收敛相关 helper 注释 - 更新 cqrs-rewrite 跟踪与 trace,记录 RP-066 的批处理结果和验证 --- GFramework.Core.Abstractions/README.md | 10 ++++-- .../Ioc/MicrosoftDiContainerTests.cs | 25 +++++++++++++++ .../Services/Modules/CqrsRuntimeModule.cs | 18 ++++++++++- GFramework.Tests.Common/CqrsTestRuntime.cs | 19 ++++++++++-- .../todos/cqrs-rewrite-migration-tracking.md | 18 +++++++++-- .../traces/cqrs-rewrite-migration-trace.md | 31 +++++++++++++++++++ docs/zh-CN/abstractions/core-abstractions.md | 7 +++-- docs/zh-CN/core/cqrs.md | 9 ++++++ 8 files changed, 126 insertions(+), 11 deletions(-) diff --git a/GFramework.Core.Abstractions/README.md b/GFramework.Core.Abstractions/README.md index cbb44371..f4f5a709 100644 --- a/GFramework.Core.Abstractions/README.md +++ b/GFramework.Core.Abstractions/README.md @@ -26,7 +26,7 @@ | --- | --- | | `Architectures/` `Lifecycle/` `Registries/` | `IArchitecture`、上下文、模块、服务模块、阶段监听、注册表基类与生命周期契约 | | `Bases/` `Controller/` `Model/` `Systems/` `Utility/` `Rule/` | 组件角色接口、优先级 / key 值对象、上下文感知约束与扩展边界 | -| `Command/` `Query/` `Cqrs/` | 旧版命令 / 查询执行器接口,以及 `ICqrsRuntime` 这类新请求模型接线契约 | +| `Command/` `Query/` `Cqrs/` | 旧版命令 / 查询执行器接口,以及面向 CQRS runtime 的兼容别名入口 | | `Events/` `Property/` `State/` `StateManagement/` | 事件总线、解绑对象、可绑定属性、状态机、Store / reducer / middleware 契约 | | `Coroutine/` `Time/` `Pause/` `Concurrency/` | 协程状态、时间源、暂停栈、键控异步锁和统计对象 | | `Resource/` `Pool/` `Logging/` `Localization/` | 资源句柄、对象池、日志、日志工厂、本地化表与格式化契约 | @@ -41,7 +41,7 @@ | 类型族 | 代表类型 | 阅读重点 | | --- | --- | --- | | `Architectures/` `Lifecycle/` `Registries/` | `IArchitecture`、`IArchitectureContext`、`IServiceModule`、`KeyValueRegistryBase` | 看架构、上下文、模块装配与注册表基类边界 | -| `Command/` `Query/` `Cqrs/` | `ICommandExecutor`、`IAsyncQueryExecutor`、`ICqrsRuntime` | 看命令、查询与新请求模型的调用入口 | +| `Command/` `Query/` `Cqrs/` | `ICommandExecutor`、`IAsyncQueryExecutor`、`ICqrsRuntime` | 看旧命令 / 查询契约,以及 CQRS runtime 的兼容别名入口 | | `Events/` `Property/` `State/` `StateManagement/` | `IEventBus`、`IBindableProperty`、`IStateMachine`、`IStore` | 看事件分发、可绑定状态与 store 契约 | | `Coroutine/` `Time/` `Pause/` `Concurrency/` | `IYieldInstruction`、`ITimeProvider`、`IPauseStackManager`、`IAsyncKeyLockManager` | 看协程、时间源、暂停栈与并发协调能力 | | `Resource/` `Pool/` `Logging/` `Localization/` | `IResourceManager`、`IObjectPoolSystem`、`ILogger`、`ILocalizationManager` | 看资源、对象池、日志与本地化服务角色 | @@ -63,7 +63,11 @@ - 架构与模块入口:`IArchitecture`、`IArchitectureContext`、`IServiceModule` - 运行时基础设施:`IIocContainer`、`ILogger`、`IResourceManager`、`IConfigurationManager` - 状态与并发能力:`IStateMachine`、`IStore`、`IAsyncKeyLockManager`、`ITimeProvider` -- 迁移与组合边界:`ICommandExecutor`、`IQueryExecutor`、`ICqrsRuntime` +- 迁移与组合边界:`ICommandExecutor`、`IQueryExecutor`,以及旧命名空间下作为 compatibility alias 暴露的 `ICqrsRuntime` + +`GFramework.Core.Abstractions.Cqrs.ICqrsRuntime` 当前主要用于兼容旧命名空间引用。新代码应直接依赖 +`GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime`,这样可以把请求模型、handler 和 runtime seam 保持在同一套 +CQRS 契约下。 ## 对应文档 diff --git a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs index 13fb782e..b9a96094 100644 --- a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs +++ b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs @@ -4,6 +4,7 @@ using GFramework.Core.Ioc; using GFramework.Core.Logging; using GFramework.Core.Tests.Cqrs; using GFramework.Core.Tests.Systems; +using GFramework.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs; using LegacyICqrsRuntime = GFramework.Core.Abstractions.Cqrs.ICqrsRuntime; @@ -171,6 +172,30 @@ public class MicrosoftDiContainerTests Assert.That(_container.Get(), Is.SameAs(_container.Get())); } + /// + /// 测试当容器里仅预注册正式 CQRS runtime seam 时,基础设施接线会补齐 legacy alias, + /// 并保持新旧服务类型解析到同一实例。 + /// + [Test] + public void RegisterInfrastructure_Should_Backfill_Legacy_Cqrs_Runtime_Alias_With_The_Same_Instance() + { + _container.Clear(); + + var runtime = CqrsRuntimeFactory.CreateRuntime( + _container, + LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher")); + _container.Register(runtime); + + Assert.That(_container.Get(), Is.Null); + + CqrsTestRuntime.RegisterInfrastructure(_container); + + Assert.That(_container.GetAll(), Has.Count.EqualTo(1)); + Assert.That(_container.GetAll(), Has.Count.EqualTo(1)); + Assert.That(_container.Get(), Is.SameAs(runtime)); + Assert.That(_container.Get(), Is.SameAs(runtime)); + } + /// /// 测试当没有实例时获取应返回 null 的功能 /// diff --git a/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs b/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs index 64076362..0bc0c25e 100644 --- a/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs +++ b/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs @@ -32,6 +32,8 @@ public sealed class CqrsRuntimeModule : IServiceModule /// /// 注册默认 CQRS runtime seam 实现。 + /// 该入口会同时补齐旧命名空间下的 ICqrsRuntime 兼容别名, + /// 并保证新旧服务类型都解析到同一个 runtime 实例。 /// /// 目标依赖注入容器。 public void Register(IIocContainer container) @@ -46,12 +48,26 @@ public sealed class CqrsRuntimeModule : IServiceModule var registrar = CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger); container.Register(runtime); - container.Register((LegacyICqrsRuntime)runtime); + RegisterLegacyRuntimeAlias(container, runtime); container.Register(registrar); container.Register( CqrsRuntimeFactory.CreateRegistrationService(registrar, registrationLogger)); } + /// + /// 为旧命名空间下的 CQRS runtime 契约注册兼容别名。 + /// + /// 承载运行时实例的依赖注入容器。 + /// 当前已创建的新 CQRS runtime 实例。 + /// + /// 旧接口仍作为兼容入口保留,因此这里明确把别名注册收敛到单独 helper, + /// 便于后续独立评估 alias 收口,而不混入 runtime 主体行为。 + /// + private static void RegisterLegacyRuntimeAlias(IIocContainer container, ICqrsRuntime runtime) + { + container.Register((LegacyICqrsRuntime)runtime); + } + /// /// 初始化模块。 /// diff --git a/GFramework.Tests.Common/CqrsTestRuntime.cs b/GFramework.Tests.Common/CqrsTestRuntime.cs index f3ca2c7c..8122f2ca 100644 --- a/GFramework.Tests.Common/CqrsTestRuntime.cs +++ b/GFramework.Tests.Common/CqrsTestRuntime.cs @@ -52,6 +52,8 @@ public static class CqrsTestRuntime /// 这使仅使用 的测试环境也能观察与生产路径一致的 runtime 行为, /// 而无需完整启动服务模块管理器。 /// 该方法按服务类型执行幂等注册,只会补齐当前容器中尚未接线的 CQRS 基础设施。 + /// 若容器里只预注册了正式 seam,本方法也会补齐旧命名空间下的 + /// 兼容别名,并保持它与正式 seam 指向同一实例。 /// public static void RegisterInfrastructure(MicrosoftDiContainer container) { @@ -63,11 +65,11 @@ public static class CqrsTestRuntime var notificationPublisher = container.Get(); var runtime = CqrsRuntimeFactory.CreateRuntime(container, runtimeLogger, notificationPublisher); container.Register(runtime); - container.Register((LegacyICqrsRuntime)runtime); + RegisterLegacyRuntimeAlias(container, runtime); } else if (container.Get() is null) { - container.Register((LegacyICqrsRuntime)container.GetRequired()); + RegisterLegacyRuntimeAlias(container, container.GetRequired()); } if (container.Get() is null) @@ -86,6 +88,19 @@ public static class CqrsTestRuntime } } + /// + /// 为旧命名空间下的 CQRS runtime 契约注册兼容别名。 + /// + /// 承载运行时实例的测试容器。 + /// 当前正式 CQRS runtime 实例。 + /// + /// 测试辅助层显式保留这条 helper,避免“已存在正式 seam 时再补旧别名”的兼容语义分散在多个分支里。 + /// + private static void RegisterLegacyRuntimeAlias(MicrosoftDiContainer container, ICqrsRuntime runtime) + { + container.Register((LegacyICqrsRuntime)runtime); + } + /// /// 通过与生产代码一致的注册入口扫描并注册指定程序集中的 CQRS 处理器。 /// diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md index 0d4a6e66..9f9a6df3 100644 --- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md +++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md @@ -7,7 +7,7 @@ CQRS 迁移与收敛。 ## 当前恢复点 -- 恢复点编号:`CQRS-REWRITE-RP-065` +- 恢复点编号:`CQRS-REWRITE-RP-066` - 当前阶段:`Phase 8` - 当前焦点: - 已完成一轮 `CQRS vs Mediator` 只读评估归档,结论已沉淀到 `archive/todos/cqrs-vs-mediator-assessment-rp063.md` @@ -34,6 +34,12 @@ CQRS 迁移与收敛。 `Mediator` 语义收口为 `CQRS` / `ArchitectureContext` - 已补充 `ArchitectureContextTests` 并发 lazy-resolution 回归,锁定 `PublishAsync(...)` 与 `CreateStream(...)` 在并发首次访问时也只会解析一次 `ICqrsRuntime` + - 已完成一轮 `LegacyICqrsRuntime` compatibility slice 收口: + - `CqrsRuntimeModule` 与 `GFramework.Tests.Common.CqrsTestRuntime` 现把 legacy alias 注册收敛到显式 helper + - `MicrosoftDiContainerTests` 已补充“只预注册正式 `ICqrsRuntime` seam 时,也会回填 legacy alias 且保持同实例”的回归 + - `GFramework.Core.Abstractions/README.md`、`docs/zh-CN/abstractions/core-abstractions.md` 与 + `docs/zh-CN/core/cqrs.md` 现已明确:旧命名空间下的 `ICqrsRuntime` 仅作为 compatibility alias 保留, + 新代码应直接依赖 `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime` - 已将 mixed fallback 场景进一步收敛:当 runtime 允许同一程序集声明多个 `CqrsReflectionFallbackAttribute` 实例时,generator 现会把可直接引用的 fallback handlers 与仅能按名称恢复的 fallback handlers 拆分发射 - `CqrsReflectionFallbackAttribute` 现允许多实例,以承载 `Type[]` 与字符串 fallback 元数据的组合输出 - 已将 generator 的程序集级 fallback 元数据进一步收敛:当全部 fallback handlers 都可直接引用且 runtime 暴露 `params Type[]` 合同时,生成器现优先发射 `typeof(...)` 形式的 fallback 元数据 @@ -216,6 +222,12 @@ CQRS 迁移与收敛。 - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests"` - 结果:通过 - 备注:`41/41` 通过;确认 CQRS 基础设施默认接线与容器行为未回归 +- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests"` + - 结果:通过 + - 备注:`42/42` 通过;本轮新增 legacy alias 回填回归后,确认正式 seam 与旧命名空间 alias 仍指向同一实例 +- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release` + - 结果:通过 + - 备注:`0 warning / 0 error`;确认 legacy alias helper 收敛与文档更新未引入 `GFramework.Core` 模块构建告警 - `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release` - 结果:通过 - 备注:`0 warning / 0 error`;确认三份 `Mediator` 命名收口后的 CQRS 测试项目构建仍然干净 @@ -226,5 +238,5 @@ CQRS 迁移与收敛。 ## 下一步 1. 基于已落地的 notification publisher seam,评估是否需要第二阶段公开配置面、并行 publisher 或 telemetry decorator -2. 继续以 `dispatch/invoker` 生成前移为优先对象,补一轮面向实现的设计评估 -3. 单独规划旧 `Command` / `Query` API 与 `LegacyICqrsRuntime` 的收口顺序;`Mediator` 测试命名收口已完成,可移出该子问题 +2. 继续以 `dispatch/invoker` 生成前移为优先对象,优先尝试 “generated request invoker provider + dispatcher fallback” 这条最小实现切片 +3. 单独规划旧 `Command` / `Query` API 的收口顺序;`LegacyICqrsRuntime` compatibility slice 已收口到显式 helper 与专门测试,可暂时移出最高优先级 diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md index f07bc614..acde156f 100644 --- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md +++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md @@ -2,6 +2,37 @@ ## 2026-04-30 +### 阶段:LegacyICqrsRuntime compatibility slice 收口(CQRS-REWRITE-RP-066) + +- 继续按 `gframework-batch-boot 50` 执行,基线仍为本地现有 `origin/main` +- 在 `RP-065` 之后复算 branch diff,相对 `origin/main` 仍为 `19 files`,明显低于 `50 files` stop condition,因此继续下一批 +- 本轮按“关键路径本地、非冲突文档委派”的方式拆成两个切片: + - worker:`GFramework.Core.Abstractions/README.md`、`docs/zh-CN/abstractions/core-abstractions.md`、`docs/zh-CN/core/cqrs.md` + - 主线程:`GFramework.Core/Services/Modules/CqrsRuntimeModule.cs`、`GFramework.Tests.Common/CqrsTestRuntime.cs`、`GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` +- 接受只读 subagent 结论后,将 `LegacyICqrsRuntime` 定位为“容器兼容层”,明确本轮不删除别名、不改 dispatcher 主体、不与旧 `Command` / `Query` API 清理混做 +- 主线程已完成: + - `CqrsRuntimeModule` 把 legacy alias 注册收敛到 `RegisterLegacyRuntimeAlias(...)` helper,并在 XML 文档里明确新旧服务类型解析到同一 runtime 实例 + - `CqrsTestRuntime.RegisterInfrastructure(...)` 现也通过同名 helper 补齐 legacy alias;当容器只预注册正式 `ICqrsRuntime` seam 时,会在幂等接线时回填旧命名空间 alias + - `MicrosoftDiContainerTests` 新增 `RegisterInfrastructure_Should_Backfill_Legacy_Cqrs_Runtime_Alias_With_The_Same_Instance`,锁定“只存在正式 seam 时也会补旧 alias,且两者仍指向同一实例”的兼容合同 +- worker 已完成文档收口: + - `GFramework.Core.Abstractions/README.md` + - `docs/zh-CN/abstractions/core-abstractions.md` + - `docs/zh-CN/core/cqrs.md` + - 三处文档都已明确:`GFramework.Core.Abstractions.Cqrs.ICqrsRuntime` 只是旧命名空间下保留的 compatibility alias,新代码应依赖 `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime` + +### 验证 + +- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests"` + - 结果:通过,`42/42` passed +- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release` + - 结果:通过,`0 warning / 0 error` + +### 当前下一步 + +1. 在保持 branch diff 低于阈值的前提下,回到 `dispatch/invoker` 生成前移主线 +2. 优先尝试只覆盖 request 路径的 generated invoker/provider 最小切片,避免一次卷入 notification / stream / pipeline executor +3. 下一次 batch 结束后继续复算 branch diff,确认距 `50 files` stop condition 的剩余 headroom + ### 阶段:测试命名收口与 ArchitectureContext lazy-resolution 回归(CQRS-REWRITE-RP-065) - 继续按 `gframework-batch-boot 50` 执行,基线仍为本地现有 `origin/main` diff --git a/docs/zh-CN/abstractions/core-abstractions.md b/docs/zh-CN/abstractions/core-abstractions.md index db399a46..82830b46 100644 --- a/docs/zh-CN/abstractions/core-abstractions.md +++ b/docs/zh-CN/abstractions/core-abstractions.md @@ -75,7 +75,10 @@ public sealed class DiagnosticsFeature - 架构与模块入口:`IArchitecture`、`IArchitectureContext`、`IServiceModule` - 运行时基础设施:`IIocContainer`、`ILogger`、`IResourceManager`、`IConfigurationManager` - 状态与并发能力:`IStateMachine`、`IStore`、`IAsyncKeyLockManager`、`ITimeProvider` -- 迁移与组合边界:`ICommandExecutor`、`IQueryExecutor`、`ICqrsRuntime` +- 迁移与组合边界:`ICommandExecutor`、`IQueryExecutor`,以及旧命名空间下作为 compatibility alias 暴露的 `ICqrsRuntime` + +`GFramework.Core.Abstractions.Cqrs.ICqrsRuntime` 当前主要承担旧命名空间兼容入口的角色。编写新模块或新增请求处理逻辑时, +应直接引用 `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime`,让 runtime seam 与 CQRS 请求契约保持一致。 ## 契约族阅读入口 @@ -85,7 +88,7 @@ public sealed class DiagnosticsFeature | --- | --- | --- | | `Architectures/` | `IArchitecture`、`IArchitectureContext`、`IArchitectureServices`、`IServiceModule` | 架构上下文、服务访问面与模块安装 / 生命周期约束 | | `Lifecycle/` `Registries/` | `ILifecycle`、`IAsyncInitializable`、`IRegistry`、`KeyValueRegistryBase` | 初始化 / 销毁阶段和注册表抽象边界 | -| `Command/` `Query/` `Cqrs/` | `ICommandExecutor`、`IAsyncCommand`、`IQueryExecutor`、`ICqrsRuntime` | 旧命令 / 查询接口与新请求模型之间的兼容和迁移边界 | +| `Command/` `Query/` `Cqrs/` | `ICommandExecutor`、`IAsyncCommand`、`IQueryExecutor`、`ICqrsRuntime` | 旧命令 / 查询接口,以及 CQRS runtime compatibility alias 的迁移边界 | | `Events/` `Property/` | `IEventBus`、`IEventFilter`、`IBindableProperty`、`IReadonlyBindableProperty` | 事件传播、过滤、解绑对象和属性订阅语义 | | `State/` `StateManagement/` | `IStateMachine`、`IAsyncState`、`IStore`、`IStoreMiddleware` | 状态机契约与 Store 的 reducer / middleware / diagnostics 边界 | | `Coroutine/` `Time/` `Pause/` `Concurrency/` | `IYieldInstruction`、`ICoroutineStatistics`、`ITimeProvider`、`IPauseStackManager`、`IAsyncKeyLockManager` | 调度模型、时间源、暂停栈和异步锁契约 | diff --git a/docs/zh-CN/core/cqrs.md b/docs/zh-CN/core/cqrs.md index 8ef81840..1fd1f601 100644 --- a/docs/zh-CN/core/cqrs.md +++ b/docs/zh-CN/core/cqrs.md @@ -13,6 +13,9 @@ description: Cqrs 模块族的运行时、契约层、生成器入口,以及 如果你在写新功能,优先使用这套请求模型,而不是继续扩展 `GFramework.Core.Command` / `Query` 的兼容层。 +如果你在查找 `ICqrsRuntime`,请把 `GFramework.Core.Abstractions.Cqrs.ICqrsRuntime` 理解为旧命名空间下保留的 +legacy compatibility alias。新代码应直接依赖 `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime`。 + ## 模块族边界 | 模块 | 角色 | 何时安装 | @@ -202,6 +205,12 @@ RegisterCqrsPipelineBehavior>(); `IArchitectureContext` 仍然兼容旧入口,但新代码应优先使用 CQRS runtime。 +这里有两个边界需要分开理解: + +- 旧 `Command` / `Query` 入口仍可用于维护历史调用链 +- 旧命名空间下的 `ICqrsRuntime` 只是为了兼容既有引用而保留的 alias;面向新代码时,应直接使用 + `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime` + 一个简单判断规则: - 在维护历史代码:允许继续使用旧 Command / Query