diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs index 9b3f889e..f74ec308 100644 --- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs +++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs @@ -1144,6 +1144,13 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator string HandlerInterfaceDisplayName, string HandlerInterfaceLogName); + /// + /// 标记某条 handler 注册语句在生成阶段采用的表达策略。 + /// + /// + /// 该枚举只服务于输出排序与代码分支选择,用来保证生成注册器在“直接注册” + /// “反射实现类型查找”和“精确运行时类型解析”之间保持稳定顺序。 + /// private enum OrderedRegistrationKind { Direct, @@ -1151,6 +1158,14 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator PreciseReflected } + /// + /// 描述生成注册器中某个运行时类型引用的构造方式。 + /// + /// + /// 某些 handler 服务类型可以直接以 typeof(...) 输出,某些则需要在运行时补充 + /// 反射查找、数组/指针封装或泛型实参重建。该记录把这些差异收敛为统一的递归结构, + /// 供源码输出阶段生成稳定的类型解析语句。 + /// private sealed record RuntimeTypeReferenceSpec( string? TypeDisplayName, string? ReflectionTypeMetadataName, @@ -1161,18 +1176,27 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator RuntimeTypeReferenceSpec? GenericTypeDefinitionReference, ImmutableArray GenericTypeArguments) { + /// + /// 创建一个可直接通过 typeof(...) 表达的类型引用。 + /// public static RuntimeTypeReferenceSpec FromDirectReference(string typeDisplayName) { return new RuntimeTypeReferenceSpec(typeDisplayName, null, null, null, 0, null, null, ImmutableArray.Empty); } + /// + /// 创建一个需要从当前消费端程序集反射解析的类型引用。 + /// public static RuntimeTypeReferenceSpec FromReflectionLookup(string reflectionTypeMetadataName) { return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, null, null, 0, null, null, ImmutableArray.Empty); } + /// + /// 创建一个需要从被引用程序集反射解析的类型引用。 + /// public static RuntimeTypeReferenceSpec FromExternalReflectionLookup( string reflectionAssemblyName, string reflectionTypeMetadataName) @@ -1182,18 +1206,27 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator ImmutableArray.Empty); } + /// + /// 创建一个数组类型引用。 + /// public static RuntimeTypeReferenceSpec FromArray(RuntimeTypeReferenceSpec elementTypeReference, int arrayRank) { return new RuntimeTypeReferenceSpec(null, null, null, elementTypeReference, arrayRank, null, null, ImmutableArray.Empty); } + /// + /// 创建一个指针类型引用。 + /// public static RuntimeTypeReferenceSpec FromPointer(RuntimeTypeReferenceSpec pointedAtTypeReference) { return new RuntimeTypeReferenceSpec(null, null, null, null, 0, pointedAtTypeReference, null, ImmutableArray.Empty); } + /// + /// 创建一个封闭泛型类型引用。 + /// public static RuntimeTypeReferenceSpec FromConstructedGeneric( RuntimeTypeReferenceSpec genericTypeDefinitionReference, ImmutableArray genericTypeArguments) diff --git a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs index 1bef8ef0..21db7821 100644 --- a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs +++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs @@ -651,32 +651,70 @@ internal static class CqrsHandlerRegistrar } } + /// + /// 描述某个程序集在生成注册器之后仍需运行时补扫的 handler 元数据。 + /// + /// + /// 该对象把“是否存在精确 fallback 类型列表”与“是否只能回退到整程序集扫描”收敛为同一份内部状态, + /// 供注册流水线后续阶段统一判断。 + /// private sealed class ReflectionFallbackMetadata(IReadOnlyList types) { + /// + /// 获取需要通过运行时反射补充注册的 handler 类型集合。 + /// public IReadOnlyList Types { get; } = types ?? throw new ArgumentNullException(nameof(types)); + /// + /// 获取当前是否持有精确的 fallback 类型清单。 + /// public bool HasExplicitTypes => Types.Count > 0; } + /// + /// 描述单个程序集在注册阶段提取到的 generated registry 与 reflection fallback 元数据。 + /// private sealed class AssemblyRegistrationMetadata( IReadOnlyList registryTypes, ReflectionFallbackMetadata? reflectionFallbackMetadata) { + /// + /// 获取程序集上声明的 generated registry 类型集合。 + /// public IReadOnlyList RegistryTypes { get; } = registryTypes ?? throw new ArgumentNullException(nameof(registryTypes)); + /// + /// 获取该程序集是否还要求运行时补充 reflection fallback。 + /// public ReflectionFallbackMetadata? ReflectionFallbackMetadata { get; } = reflectionFallbackMetadata; } + /// + /// 缓存 generated registry 激活所需的类型判定结果与工厂委托。 + /// + /// + /// 该缓存把“是否实现契约”“是否为抽象类型”“是否已构建激活委托”封装为不可变快照, + /// 避免对同一 registry 类型重复执行反射分析。 + /// private sealed class RegistryActivationMetadata( bool implementsRegistryContract, bool isAbstract, Func? factory) { + /// + /// 获取目标类型是否实现了 。 + /// public bool ImplementsRegistryContract { get; } = implementsRegistryContract; + /// + /// 获取目标类型是否为抽象类型。 + /// public bool IsAbstract { get; } = isAbstract; + /// + /// 获取可用于实例化 registry 的工厂委托。 + /// public Func? Factory { get; } = factory; } } diff --git a/ai-plan/public/documentation-full-coverage-governance/todos/documentation-full-coverage-governance-tracking.md b/ai-plan/public/documentation-full-coverage-governance/todos/documentation-full-coverage-governance-tracking.md index 28ca5186..350e49a0 100644 --- a/ai-plan/public/documentation-full-coverage-governance/todos/documentation-full-coverage-governance-tracking.md +++ b/ai-plan/public/documentation-full-coverage-governance/todos/documentation-full-coverage-governance-tracking.md @@ -12,12 +12,12 @@ ## 当前恢复点 -- 恢复点编号:`DOCUMENTATION-FULL-COVERAGE-GOV-RP-003` -- 当前阶段:`Phase 3 - Cqrs Docs Refresh Preparation` +- 恢复点编号:`DOCUMENTATION-FULL-COVERAGE-GOV-RP-004` +- 当前阶段:`Phase 3 - Cqrs Docs Refresh` - 当前焦点: - - 准备进入 `Cqrs` / `Cqrs.Abstractions` / `Cqrs.SourceGenerators` 波次 + - 收口 `Cqrs` / `Cqrs.Abstractions` / `Cqrs.SourceGenerators` 的 landing / generator topic / API 入口 - 延续 `README / landing / API reference / XML inventory` 的同一治理模板 - - 继续把模块族 inventory 从“入口存在”推进到“可审计的 XML / README / landing 对照表” + - 为下一波 `Game` family 审计保留统一的恢复模板与验证口径 ## 当前状态摘要 @@ -43,13 +43,17 @@ - 重写 `docs/zh-CN/ecs/arch.md`,明确 `UseArch(...)` 需早于 `Initialize()` 的真实接入时机 - 刷新 `GFramework.Ecs.Arch/README.md`,使运行时 README 与源码 / 测试一致 - 为 `GFramework.Ecs.Arch.Abstractions/README.md` 与 `docs/zh-CN/abstractions/ecs-arch-abstractions.md` 补齐类型族级 XML inventory + - 重写 `docs/zh-CN/core/cqrs.md`,将其收敛为 `Cqrs` family landing,并补齐运行时 / 契约层 / 生成器的 XML inventory + - 新建 `docs/zh-CN/source-generators/cqrs-handler-registry-generator.md`,为 `Cqrs.SourceGenerators` 补齐站内专题入口 + - 更新 `docs/zh-CN/source-generators/index.md`、`docs/zh-CN/api-reference/index.md` 与 VitePress sidebar,使 `Cqrs` family 的 generator 入口可导航 + - 为 `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 与 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 中缺失的内部类型补齐 XML 注释,使本轮轻量 inventory 达到声明级闭环 ## Inventory(第一版) | 模块族 | 当前状态 | 当前证据 | 下一动作 | | --- | --- | --- | --- | | `Core` / `Core.Abstractions` | `README / landing / 类型族级 XML inventory 已收口,成员级审计待补齐` | 根 README、模块 README、`docs/zh-CN/core/**`、`docs/zh-CN/abstractions/core-abstractions.md` 已对齐当前目录与类型族基线 | 进入巡检;如有新 API 变更,再追加成员级 XML 审计 | -| `Cqrs` / `Cqrs.Abstractions` / `Cqrs.SourceGenerators` | `待重写` | README 已存在;站内入口目前分散在 `docs/zh-CN/core/cqrs.md` 与 `docs/zh-CN/source-generators/**` | 进入下一波次,补 dedicated landing / API map 审计 | +| `Cqrs` / `Cqrs.Abstractions` / `Cqrs.SourceGenerators` | `README / landing / generator topic / 类型族级 XML inventory 已收口,成员级审计待补齐` | `GFramework.Cqrs/README.md`、`GFramework.Cqrs.Abstractions/README.md`、`GFramework.Cqrs.SourceGenerators/README.md`、`docs/zh-CN/core/cqrs.md`、`docs/zh-CN/source-generators/cqrs-handler-registry-generator.md`、`docs/zh-CN/api-reference/index.md` 已对齐当前源码与测试 | 转入巡检;下一波切到 `Game` family 的 XML / 教程链路审计 | | `Game` / `Game.Abstractions` / `Game.SourceGenerators` | `已验证` | 根 README、模块 README、`docs/zh-CN/game/**` 和 abstractions 页已存在 | 后续波次补 XML / 教程链路审计 | | `Godot` / `Godot.SourceGenerators` | `已验证` | 上一轮归档 topic 已完成核心 landing / topic / tutorial 校验 | 进入巡检周期,重点看回漂 | | `Ecs.Arch` / `Ecs.Arch.Abstractions` | `README / landing / abstractions / 类型族级 XML inventory 已收口,成员级审计待补齐` | `GFramework.Ecs.Arch/README.md`、`GFramework.Ecs.Arch.Abstractions/README.md`、`docs/zh-CN/ecs/**`、`docs/zh-CN/abstractions/ecs-arch-abstractions.md` 已对齐当前源码与测试 | 转入巡检;后续仅在运行时公共 API 变动时补成员级 XML 细审 | @@ -70,8 +74,6 @@ ## 当前风险 -- `Cqrs` family 目前仍缺 dedicated landing 与统一 API / XML 阅读链路,站内入口散落在 `core` 与 `source-generators` 栏目 - - 缓解措施:下一恢复点直接进入 `Cqrs` 波次,按 `Core` / `Ecs` 已验证模板重写入口 - 当前 `Core` / `Core.Abstractions` 只完成了类型族级 XML 基线,不等于成员级契约全审计 - 缓解措施:后续只在共享抽象或高风险生命周期接口发生改动时补成员级细审,不在本轮扩张范围 - 其他模块族尚未全部建立同粒度的 XML inventory @@ -80,6 +82,8 @@ - 缓解措施:将本 topic 作为长期 active topic 保留,并在后续巡检中记录回漂来源 - VitePress 页面不能直接链接到 `docs/` 目录之外的模块 `README.md` - 缓解措施:站内页面用模块路径文本或站内 API 入口表达,仓库级 README 仍保留仓库文件链接 +- `GFramework.Cqrs` 在当前 WSL / dotnet 环境下,本地 build 仍会读取失效的 fallback package folder 配置,导致无法完成该项目的标准编译验证 + - 缓解措施:本轮先以 `GFramework.Cqrs.SourceGenerators` 编译通过和 docs site build 通过作为有效验证,并在后续环境治理或构建脚本清理时单独处理 `RestoreFallbackFolders` / 资产文件问题 ## 验证说明 @@ -116,9 +120,27 @@ - `cd docs && bun run build` - 结果:通过 - 备注:`2026-04-22` 在 Ecs 波次重写后重新构建通过;仅保留 VitePress 大 chunk warning,无构建失败 +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/core/cqrs.md` +- 结果:通过 +- 备注:`2026-04-22` 在重写 `Cqrs` family landing 后重新验证 +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators/cqrs-handler-registry-generator.md` +- 结果:通过 +- 备注:`2026-04-22` 在新增 `Cqrs.SourceGenerators` 专题页后验证通过 +- `python3` 轻量 XML inventory 扫描 +- 结果:通过 +- 备注:`2026-04-22` 确认 `GFramework.Cqrs` 的 `Internal/` 为 `14/14`、`GFramework.Cqrs.SourceGenerators/Cqrs/` 为 `3/3`、`GFramework.Cqrs.Abstractions/Cqrs/` 为 `20/20` +- `DOTNET_CLI_HOME=/tmp/dotnet-home dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release -p:RestoreFallbackFolders=` +- 结果:通过 +- 备注:保留既有 `NU1900` 与 `MA0051` warnings;无新增编译错误 +- `DOTNET_CLI_HOME=/tmp/dotnet-home dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release` +- 结果:失败 +- 备注:当前环境会命中失效的 Windows fallback package folder,并在多目标 inner build 阶段触发 `MSB4276` / `MSB4018`;失败原因已记录为环境阻塞,不属于本轮文档改动回归 +- `cd docs && bun run build` +- 结果:通过 +- 备注:`2026-04-22` 在 `Cqrs` 波次文档刷新后重新构建通过;仅保留 VitePress 大 chunk warning,无构建失败 ## 下一步 -1. 进入 `Cqrs` 波次,梳理 `Cqrs` / `Cqrs.Abstractions` / `Cqrs.SourceGenerators` 的模块边界与 docs 入口 -2. 判断是否需要为 `Cqrs` family 新建 dedicated landing,或把现有 `core/cqrs.md` 拆分成模块族入口页 -3. 继续为每个模块族补“README / landing / tutorials / API reference / XML”对照表,持续清零 `P0` / `P1` +1. 切换到 `Game` / `Game.Abstractions` / `Game.SourceGenerators` 波次,按 `Cqrs` 模板核对 README / landing / tutorials / API reference / XML 链路 +2. 评估 `Game` family 当前是否已经具备类型族级 XML inventory,还是仍停留在“README / 页面存在但不可审计” +3. 在后续环境治理任务中单独处理 `GFramework.Cqrs` 本地 build 的 fallback package folder 阻塞,避免影响后续代码类验证 diff --git a/ai-plan/public/documentation-full-coverage-governance/traces/documentation-full-coverage-governance-trace.md b/ai-plan/public/documentation-full-coverage-governance/traces/documentation-full-coverage-governance-trace.md index bc9aab68..7b541418 100644 --- a/ai-plan/public/documentation-full-coverage-governance/traces/documentation-full-coverage-governance-trace.md +++ b/ai-plan/public/documentation-full-coverage-governance/traces/documentation-full-coverage-governance-trace.md @@ -99,3 +99,45 @@ 1. 在 `Cqrs` 波次核对模块 README、`docs/zh-CN/core/cqrs.md` 与 `docs/zh-CN/source-generators/**` 的真实 owner 2. 决定 `Cqrs` family 是补 dedicated landing 还是拆分现有入口页 + +### 当前恢复点:RP-004 + +- 完成 `Cqrs` 波次的模块族入口刷新: + - 重写 `docs/zh-CN/core/cqrs.md` + - 新建 `docs/zh-CN/source-generators/cqrs-handler-registry-generator.md` + - 更新 `docs/zh-CN/source-generators/index.md` + - 更新 `docs/zh-CN/api-reference/index.md` + - 更新 `docs/.vitepress/config.mts` +- 将 `Cqrs` family 从“README 已存在但 generator 入口分散”推进到“runtime / abstractions / source generator 都有明确站内入口” +- 为 `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 与 + `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 中缺失的内部类型补齐 XML 注释 +- 基于轻量扫描确认: + - `GFramework.Cqrs.Abstractions/Cqrs/` 当前类型声明级 XML 覆盖为 `20/20` + - `GFramework.Cqrs` 根入口与 `Internal/` 已补到 `19/19` + - `GFramework.Cqrs.SourceGenerators/Cqrs/` 当前类型声明级 XML 覆盖为 `3/3` + +### 当前决策(RP-004) + +- `docs/zh-CN/core/cqrs.md` 继续保留在 `Core` 栏目,但其角色调整为 `Cqrs` family landing,而不再只是 runtime 简介页 +- `Cqrs.SourceGenerators` 不单独新建一级导航栏目,而是在 `source-generators` 栏目内补一个专用专题页,保持站点 taxonomy 稳定 +- generator 入口以“专题页 + API reference 链接 + sidebar”三点联动,而不是只在 `source-generators/index.md` 留一个段落链接 +- XML inventory 仍维持“类型声明级基线”口径,不在本轮扩展成成员级 `param/returns/exception` 细审 + +### 当前验证(RP-004) + +- 文档校验: + - `validate-all.sh docs/zh-CN/core/cqrs.md`:通过 + - `validate-all.sh docs/zh-CN/source-generators/cqrs-handler-registry-generator.md`:通过 +- 轻量 XML inventory: + - `GFramework.Cqrs/Internal/`:`14/14` + - `GFramework.Cqrs.Abstractions/Cqrs/`:`20/20` + - `GFramework.Cqrs.SourceGenerators/Cqrs/`:`3/3` +- 构建校验: + - `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release -p:RestoreFallbackFolders=`:通过 + - `cd docs && bun run build`:通过;仅保留 VitePress 大 chunk warning,无构建失败 + - `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`:失败;当前 WSL / dotnet 环境仍引用失效的 Windows fallback package folder,并在多目标 inner build 阶段触发 `MSB4276` / `MSB4018` + +### 下一步 + +1. 切换到 `Game` family 波次,按 `Core` / `Ecs` / `Cqrs` 已验证模板继续补 XML inventory 与教程链路 +2. 把 `GFramework.Cqrs` 的本地构建阻塞留给后续环境治理或构建脚本清理,不在本 topic 内扩张为环境修复任务 diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 83795043..b8aeaf5a 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -252,6 +252,7 @@ export default defineConfig({ { text: 'ContextAware 生成器', link: '/zh-CN/source-generators/context-aware-generator' }, { text: 'Priority 生成器', link: '/zh-CN/source-generators/priority-generator' }, { text: 'Context Get 注入', link: '/zh-CN/source-generators/context-get-generator' }, + { text: 'CQRS Handler Registry', link: '/zh-CN/source-generators/cqrs-handler-registry-generator' }, { text: 'Godot 项目元数据', link: '/zh-CN/source-generators/godot-project-generator' }, { text: 'GetNode 生成器 (Godot)', link: '/zh-CN/source-generators/get-node-generator' }, { text: 'BindNodeSignal 生成器 (Godot)', link: '/zh-CN/source-generators/bind-node-signal-generator' } diff --git a/docs/zh-CN/api-reference/index.md b/docs/zh-CN/api-reference/index.md index 02d63fd1..14ea32b8 100644 --- a/docs/zh-CN/api-reference/index.md +++ b/docs/zh-CN/api-reference/index.md @@ -30,7 +30,7 @@ description: GFramework 的 API 阅读入口,按模块映射 README、专题 | 模块族 | 模块 README | 站内入口 | XML 文档关注点 | | --- | --- | --- | --- | | `Core` / `Core.Abstractions` | `GFramework.Core/README.md`、`GFramework.Core.Abstractions/README.md` | [`../core/index.md`](../core/index.md)、[`../abstractions/core-abstractions.md`](../abstractions/core-abstractions.md) | 架构入口、生命周期、命令 / 查询 / 事件 / 状态 / 资源 / 日志 / 配置 / 并发契约 | -| `Cqrs` / `Cqrs.Abstractions` / `Cqrs.SourceGenerators` | `GFramework.Cqrs/README.md`、`GFramework.Cqrs.Abstractions/README.md`、`GFramework.Cqrs.SourceGenerators/README.md` | [`../core/cqrs.md`](../core/cqrs.md)、[`../source-generators/index.md`](../source-generators/index.md) | request / notification / handler / pipeline / registry contract | +| `Cqrs` / `Cqrs.Abstractions` / `Cqrs.SourceGenerators` | `GFramework.Cqrs/README.md`、`GFramework.Cqrs.Abstractions/README.md`、`GFramework.Cqrs.SourceGenerators/README.md` | [`../core/cqrs.md`](../core/cqrs.md)、[`../source-generators/cqrs-handler-registry-generator.md`](../source-generators/cqrs-handler-registry-generator.md) | request / notification / handler / pipeline / registry / fallback contract | | `Game` / `Game.Abstractions` / `Game.SourceGenerators` | `GFramework.Game/README.md`、`GFramework.Game.Abstractions/README.md`、`GFramework.Game.SourceGenerators/README.md` | [`../game/index.md`](../game/index.md)、[`../abstractions/game-abstractions.md`](../abstractions/game-abstractions.md) | 配置、数据、设置、场景、UI、存储、序列化契约 | | `Godot` / `Godot.SourceGenerators` | `GFramework.Godot/README.md`、`GFramework.Godot.SourceGenerators/README.md` | [`../godot/index.md`](../godot/index.md)、[`../source-generators/index.md`](../source-generators/index.md) | 节点扩展、场景 / UI 适配、资源 / 存储 / 日志接入 | | `Ecs.Arch` / `Ecs.Arch.Abstractions` | `GFramework.Ecs.Arch/README.md`、`GFramework.Ecs.Arch.Abstractions/README.md` | [`../ecs/index.md`](../ecs/index.md)、[`../ecs/arch.md`](../ecs/arch.md)、[`../abstractions/ecs-arch-abstractions.md`](../abstractions/ecs-arch-abstractions.md) | ECS 模块契约、系统适配、配置对象和运行时装配边界 | diff --git a/docs/zh-CN/core/cqrs.md b/docs/zh-CN/core/cqrs.md index 8d9158ec..28653d22 100644 --- a/docs/zh-CN/core/cqrs.md +++ b/docs/zh-CN/core/cqrs.md @@ -1,15 +1,29 @@ --- title: CQRS -description: 当前推荐的新请求模型,统一覆盖 command、query、notification、stream request 和 pipeline behaviors。 +description: Cqrs 模块族的运行时、契约层、生成器入口,以及 XML / API 阅读链路。 --- # CQRS -`GFramework.Cqrs` 是当前推荐的新请求模型 runtime。 +`Cqrs` 栏目对应三个直接相关的消费模块: -如果你在写新功能,优先使用这套模型,而不是继续扩展 `GFramework.Core.Command` / `Query` 的兼容层。 +- `GFramework.Cqrs` +- `GFramework.Cqrs.Abstractions` +- `GFramework.Cqrs.SourceGenerators` -## 安装方式 +如果你在写新功能,优先使用这套请求模型,而不是继续扩展 `GFramework.Core.Command` / `Query` 的兼容层。 + +## 模块族边界 + +| 模块 | 角色 | 何时安装 | +| --- | --- | --- | +| `GeWuYou.GFramework.Cqrs.Abstractions` | 纯契约层,定义 request、notification、stream、handler、pipeline、runtime seam | 需要把消息契约放到更稳定的共享层,或只依赖接口做解耦 | +| `GeWuYou.GFramework.Cqrs` | 默认 runtime,提供 dispatcher、handler 基类、上下文扩展和程序集注册流程 | 大多数直接消费 CQRS 的业务模块 | +| `GeWuYou.GFramework.Cqrs.SourceGenerators` | 编译期生成 `ICqrsHandlerRegistry`,缩小运行时反射扫描范围 | handler 较多,想把注册映射前移到编译期 | + +## 最小接入路径 + +最小安装组合是: ```bash dotnet add package GeWuYou.GFramework.Cqrs @@ -22,15 +36,6 @@ dotnet add package GeWuYou.GFramework.Cqrs.Abstractions dotnet add package GeWuYou.GFramework.Cqrs.SourceGenerators ``` -## 先理解分层 - -- `GFramework.Cqrs.Abstractions` - - 纯契约层,定义请求、处理器、行为等接口 -- `GFramework.Cqrs` - - 默认 runtime、dispatcher、处理器基类和上下文扩展 -- `GFramework.Cqrs.SourceGenerators` - - 可选生成器,为消费端程序集生成 `ICqrsHandlerRegistry` - ## 最小示例 消息基类和处理器基类在不同命名空间: @@ -38,12 +43,10 @@ dotnet add package GeWuYou.GFramework.Cqrs.SourceGenerators - 消息基类:`GFramework.Cqrs.Command` / `Query` / `Notification` - 处理器基类:`GFramework.Cqrs.Cqrs.Command` / `Query` / `Notification` -示例: - ```csharp +using GFramework.Cqrs.Abstractions.Cqrs.Command; using GFramework.Cqrs.Command; using GFramework.Cqrs.Cqrs.Command; -using GFramework.Cqrs.Abstractions.Cqrs.Command; public sealed record CreatePlayerInput(string Name) : ICommandInput; @@ -66,9 +69,7 @@ public sealed class CreatePlayerCommandHandler } ``` -## 发送请求 - -如果你在 `IContextAware` 对象内部: +如果你在 `IContextAware` 对象内部发送请求: ```csharp using GFramework.Cqrs.Extensions; @@ -77,7 +78,7 @@ var playerId = await this.SendAsync( new CreatePlayerCommand(new CreatePlayerInput("Alice"))); ``` -如果你在组合根或测试里: +如果你在组合根或测试里发送请求: ```csharp var playerId = await architecture.Context.SendRequestAsync( @@ -92,7 +93,7 @@ var playerId = await architecture.Context.SendRequestAsync( - `PublishAsync(...)` - `CreateStream(...)` -## 查询、通知和流 +## 统一请求模型 这套 runtime 不只处理 command,也统一处理: @@ -103,9 +104,9 @@ var playerId = await architecture.Context.SendRequestAsync( - Stream Request - 返回 `IAsyncEnumerable` -也就是说,新代码通常不需要再分别设计“命令总线”“查询总线”和另一套通知分发语义。 +新代码通常不需要再分别设计“命令总线”“查询总线”和另一套通知分发语义。 -## 注册处理器 +## 处理器注册与生成器协作 在标准 `Architecture` 启动路径中,CQRS runtime 会自动接入基础设施。你通常只需要在 `OnInitialize()` 里追加行为或额外程序集: @@ -123,11 +124,15 @@ protected override void OnInitialize() } ``` -默认逻辑会: +默认注册流程当前遵循这些语义: -1. 优先使用消费端程序集上的生成注册器 -2. 生成注册器不可用时回退到反射扫描 -3. 对同一程序集去重,避免重复注册 +1. 优先读取消费端程序集上的 `CqrsHandlerRegistryAttribute` +2. 存在生成注册器时优先使用 `ICqrsHandlerRegistry` +3. 生成注册器不可用或元数据异常时记录告警并回退到反射路径 +4. 如果程序集带有 `CqrsReflectionFallbackAttribute`,只补扫剩余 handler +5. 同一程序集按稳定键去重,避免重复注册 + +`Cqrs.SourceGenerators` 的专题入口见 [../source-generators/cqrs-handler-registry-generator.md](../source-generators/cqrs-handler-registry-generator.md)。 ## Pipeline Behavior @@ -145,7 +150,7 @@ RegisterCqrsPipelineBehavior>(); - 审计 - 重试或统一异常封装 -旧的 `Mediator` 兼容别名入口已经移除;当前公开入口只有 `RegisterCqrsPipelineBehavior()`。 +当前公开入口只有 `RegisterCqrsPipelineBehavior()`。 ## 和旧 Command / Query 的关系 @@ -157,15 +162,28 @@ RegisterCqrsPipelineBehavior>(); - 新路径 - `GFramework.Cqrs` -`IArchitectureContext` 仍然会兼容旧入口,但新代码应优先使用 CQRS runtime。 +`IArchitectureContext` 仍然兼容旧入口,但新代码应优先使用 CQRS runtime。 一个简单判断规则: - 在维护历史代码:允许继续使用旧 Command / Query - 在写新功能或新模块:优先使用 CQRS +## XML 覆盖基线 + +下面这份 inventory 记录的是 `2026-04-22` 对 `Cqrs` 家族做的一轮轻量 XML 盘点结果:只统计当前运行时、契约层和生成器入口中的类型声明级 XML 覆盖,用来校对 README、landing page 与 API 入口,不把它表述成成员级契约全审计。 + +| 类型族 | 基线状态 | 代表类型 | 阅读重点 | +| --- | --- | --- | --- | +| `GFramework.Cqrs.Abstractions/Cqrs/` | `20/20` 个类型声明已带 XML 注释 | `ICqrsRuntime`、`ICqrsHandlerRegistrar`、`IPipelineBehavior<,>`、`IRequestHandler<,>`、`Unit` | 先看请求、处理器和 runtime seam 的最小契约 | +| `GFramework.Cqrs/Command` `Query` `Notification` `Request` `Extensions` | `7/7` 个类型声明已带 XML 注释 | `CommandBase`、`QueryBase`、`NotificationBase`、`ContextAwareCqrsExtensions` | 看业务侧常用基类和上下文发送入口 | +| `GFramework.Cqrs/Cqrs/` | `12/12` 个类型声明已带 XML 注释 | `AbstractCommandHandler<,>`、`AbstractQueryHandler<,>`、`AbstractNotificationHandler<>`、`LoggingBehavior<,>` | 看默认处理器基类、上下文注入与行为管道 | +| `GFramework.Cqrs` 根入口与 `Internal/` | `19/19` 个类型声明已带 XML 注释 | `CqrsRuntimeFactory`、`ICqrsHandlerRegistry`、`CqrsHandlerRegistryAttribute`、`CqrsReflectionFallbackAttribute`、`DefaultCqrsRegistrationService` | 看 runtime 创建入口、registry 协议、fallback 语义和程序集去重规则 | +| `GFramework.Cqrs.SourceGenerators/Cqrs/` | `3/3` 个类型声明已带 XML 注释 | `CqrsHandlerRegistryGenerator`、`RuntimeTypeReferenceSpec`、`OrderedRegistrationKind` | 看生成注册器、精确 type lookup 和 fallback 诊断边界 | + ## 继续阅读 - 架构入口:[architecture](./architecture.md) - 上下文入口:[context](./context.md) +- 生成器专题:[../source-generators/cqrs-handler-registry-generator.md](../source-generators/cqrs-handler-registry-generator.md) - 模块 README:`GFramework.Cqrs/README.md` diff --git a/docs/zh-CN/source-generators/cqrs-handler-registry-generator.md b/docs/zh-CN/source-generators/cqrs-handler-registry-generator.md new file mode 100644 index 00000000..3069b7d0 --- /dev/null +++ b/docs/zh-CN/source-generators/cqrs-handler-registry-generator.md @@ -0,0 +1,127 @@ +--- +title: CQRS Handler Registry 生成器 +description: 为消费端程序集生成 CQRS handler registry,并在需要时附带精确 reflection fallback 元数据。 +--- + +# CQRS Handler Registry 生成器 + +`GFramework.Cqrs.SourceGenerators` 会在编译期为当前业务程序集生成 `ICqrsHandlerRegistry`,让 `GFramework.Cqrs` +runtime 在注册 handlers 时优先走静态注册表,而不是先扫描整个程序集。 + +它服务的是 `Cqrs` 家族,不是独立运行时: + +- 契约层:`GeWuYou.GFramework.Cqrs.Abstractions` +- 默认 runtime:`GeWuYou.GFramework.Cqrs` +- 编译期生成器:`GeWuYou.GFramework.Cqrs.SourceGenerators` + +## 生成什么 + +当前生成器会分析消费端程序集中的: + +- `IRequestHandler<,>` +- `INotificationHandler<>` +- `IStreamRequestHandler<,>` + +然后输出两类结果: + +1. 一个实现 `ICqrsHandlerRegistry` 的内部注册器类型 +2. 程序集级 `CqrsHandlerRegistryAttribute` + +当某些 handler 不能被生成代码安全地直接引用时,还会补发: + +- 程序集级 `CqrsReflectionFallbackAttribute` + +这意味着运行时会先使用生成注册器完成可静态表达的映射,再只对剩余类型做补扫,而不是退回整程序集盲扫。 + +## 最小接入路径 + +安装方式保持 runtime 包与生成器包版本一致,并把生成器作为编译期依赖引入: + +```xml + + + + + +``` + +运行时侧仍然按 `Core` 的标准入口注册程序集: + +```csharp +protected override void OnInitialize() +{ + RegisterCqrsHandlersFromAssembly(typeof(GameArchitecture).Assembly); +} +``` + +如果你的 handlers 分布在多个业务程序集里,则改用: + +```csharp +RegisterCqrsHandlersFromAssemblies( +[ + typeof(InventoryCqrsMarker).Assembly, + typeof(BattleCqrsMarker).Assembly +]); +``` + +## 运行时如何消费生成结果 + +`Cqrs` runtime 当前的注册顺序是: + +1. 先读取程序集上的 `CqrsHandlerRegistryAttribute` +2. 优先激活生成的 `ICqrsHandlerRegistry` +3. 若生成元数据损坏、registry 不可激活,记录告警并回退到反射路径 +4. 若存在 `CqrsReflectionFallbackAttribute`,只补扫剩余 handler +5. 同一程序集按稳定键去重,避免重复注册 + +这个行为由 `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` 和 +`GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 共同覆盖。 + +## 什么时候值得安装 + +推荐安装: + +- 业务程序集内 handler 数量较多 +- 想把 handler 注册路径前移到编译期 +- 希望冷启动阶段减少整程序集反射扫描 +- 需要更明确地观察“哪些 handler 走静态注册,哪些只能走 fallback” + +可以先不装: + +- 项目体量很小,handler 很少 +- 当前只做原型,尚不关心注册成本 +- 你还没稳定到 `Cqrs` runtime 的最终接入边界 + +## fallback 边界 + +生成器并不会承诺“所有 handler 都能被静态表达”。 + +当前实现遵循一个保守原则: + +- 能直接引用的 handler,生成直接注册语句 +- 实现类型不能直接引用、但服务接口还能精确表达时,生成反射实现类型查找 +- 服务接口本身也需要运行时解析时,生成精确 type lookup +- 只有在 runtime 提供 `CqrsReflectionFallbackAttribute` 合同时,才允许发射依赖 fallback 的结果 + +如果当前编译环境缺少这个 fallback 合同,而某些 handler 又必须依赖它,生成器会报: + +- `GF_Cqrs_001` + +这条诊断的含义不是“某个 handler 写错了”,而是“当前 runtime 合同不足以安全承载这轮生成结果”。 + +## XML / API 阅读入口 + +如果你要核对生成器对外暴露的契约,优先看这些类型: + +- `GFramework.Cqrs.ICqrsHandlerRegistry` +- `GFramework.Cqrs.CqrsHandlerRegistryAttribute` +- `GFramework.Cqrs.CqrsReflectionFallbackAttribute` +- `GFramework.Cqrs.SourceGenerators.Cqrs.CqrsHandlerRegistryGenerator` + +模块族入口见: + +- [../core/cqrs.md](../core/cqrs.md) +- [./index.md](./index.md) diff --git a/docs/zh-CN/source-generators/index.md b/docs/zh-CN/source-generators/index.md index cb07dba2..279762e2 100644 --- a/docs/zh-CN/source-generators/index.md +++ b/docs/zh-CN/source-generators/index.md @@ -65,7 +65,9 @@ GFramework 当前发布的生成器包是: - 配置 schema 生成与运行时接法: - [../game/config-system.md](../game/config-system.md) -- CQRS registry 生成入口: +- CQRS handler registry 生成器: + - [cqrs-handler-registry-generator](./cqrs-handler-registry-generator.md) +- CQRS 模块族采用入口: - [../core/cqrs.md](../core/cqrs.md) ### Godot 专用生成器