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 专用生成器