docs(cqrs): 刷新 Cqrs 文档入口

- 更新 Cqrs family landing、API 参考与 source-generators 导航

- 新增 CQRS handler registry 专题页并补充 XML inventory

- 补充 runtime 与 generator 内部类型 XML 注释

- 记录 RP-004 验证结果与后续恢复点
This commit is contained in:
gewuyou 2026-04-22 18:51:05 +08:00
parent da0ae700a3
commit a1dbed3c8d
9 changed files with 324 additions and 41 deletions

View File

@ -1144,6 +1144,13 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
string HandlerInterfaceDisplayName,
string HandlerInterfaceLogName);
/// <summary>
/// 标记某条 handler 注册语句在生成阶段采用的表达策略。
/// </summary>
/// <remarks>
/// 该枚举只服务于输出排序与代码分支选择,用来保证生成注册器在“直接注册”
/// “反射实现类型查找”和“精确运行时类型解析”之间保持稳定顺序。
/// </remarks>
private enum OrderedRegistrationKind
{
Direct,
@ -1151,6 +1158,14 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
PreciseReflected
}
/// <summary>
/// 描述生成注册器中某个运行时类型引用的构造方式。
/// </summary>
/// <remarks>
/// 某些 handler 服务类型可以直接以 <c>typeof(...)</c> 输出,某些则需要在运行时补充
/// 反射查找、数组/指针封装或泛型实参重建。该记录把这些差异收敛为统一的递归结构,
/// 供源码输出阶段生成稳定的类型解析语句。
/// </remarks>
private sealed record RuntimeTypeReferenceSpec(
string? TypeDisplayName,
string? ReflectionTypeMetadataName,
@ -1161,18 +1176,27 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
RuntimeTypeReferenceSpec? GenericTypeDefinitionReference,
ImmutableArray<RuntimeTypeReferenceSpec> GenericTypeArguments)
{
/// <summary>
/// 创建一个可直接通过 <c>typeof(...)</c> 表达的类型引用。
/// </summary>
public static RuntimeTypeReferenceSpec FromDirectReference(string typeDisplayName)
{
return new RuntimeTypeReferenceSpec(typeDisplayName, null, null, null, 0, null, null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
/// <summary>
/// 创建一个需要从当前消费端程序集反射解析的类型引用。
/// </summary>
public static RuntimeTypeReferenceSpec FromReflectionLookup(string reflectionTypeMetadataName)
{
return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, null, null, 0, null, null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
/// <summary>
/// 创建一个需要从被引用程序集反射解析的类型引用。
/// </summary>
public static RuntimeTypeReferenceSpec FromExternalReflectionLookup(
string reflectionAssemblyName,
string reflectionTypeMetadataName)
@ -1182,18 +1206,27 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
/// <summary>
/// 创建一个数组类型引用。
/// </summary>
public static RuntimeTypeReferenceSpec FromArray(RuntimeTypeReferenceSpec elementTypeReference, int arrayRank)
{
return new RuntimeTypeReferenceSpec(null, null, null, elementTypeReference, arrayRank, null, null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
/// <summary>
/// 创建一个指针类型引用。
/// </summary>
public static RuntimeTypeReferenceSpec FromPointer(RuntimeTypeReferenceSpec pointedAtTypeReference)
{
return new RuntimeTypeReferenceSpec(null, null, null, null, 0, pointedAtTypeReference, null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
/// <summary>
/// 创建一个封闭泛型类型引用。
/// </summary>
public static RuntimeTypeReferenceSpec FromConstructedGeneric(
RuntimeTypeReferenceSpec genericTypeDefinitionReference,
ImmutableArray<RuntimeTypeReferenceSpec> genericTypeArguments)

View File

@ -651,32 +651,70 @@ internal static class CqrsHandlerRegistrar
}
}
/// <summary>
/// 描述某个程序集在生成注册器之后仍需运行时补扫的 handler 元数据。
/// </summary>
/// <remarks>
/// 该对象把“是否存在精确 fallback 类型列表”与“是否只能回退到整程序集扫描”收敛为同一份内部状态,
/// 供注册流水线后续阶段统一判断。
/// </remarks>
private sealed class ReflectionFallbackMetadata(IReadOnlyList<Type> types)
{
/// <summary>
/// 获取需要通过运行时反射补充注册的 handler 类型集合。
/// </summary>
public IReadOnlyList<Type> Types { get; } = types ?? throw new ArgumentNullException(nameof(types));
/// <summary>
/// 获取当前是否持有精确的 fallback 类型清单。
/// </summary>
public bool HasExplicitTypes => Types.Count > 0;
}
/// <summary>
/// 描述单个程序集在注册阶段提取到的 generated registry 与 reflection fallback 元数据。
/// </summary>
private sealed class AssemblyRegistrationMetadata(
IReadOnlyList<Type> registryTypes,
ReflectionFallbackMetadata? reflectionFallbackMetadata)
{
/// <summary>
/// 获取程序集上声明的 generated registry 类型集合。
/// </summary>
public IReadOnlyList<Type> RegistryTypes { get; } =
registryTypes ?? throw new ArgumentNullException(nameof(registryTypes));
/// <summary>
/// 获取该程序集是否还要求运行时补充 reflection fallback。
/// </summary>
public ReflectionFallbackMetadata? ReflectionFallbackMetadata { get; } = reflectionFallbackMetadata;
}
/// <summary>
/// 缓存 generated registry 激活所需的类型判定结果与工厂委托。
/// </summary>
/// <remarks>
/// 该缓存把“是否实现契约”“是否为抽象类型”“是否已构建激活委托”封装为不可变快照,
/// 避免对同一 registry 类型重复执行反射分析。
/// </remarks>
private sealed class RegistryActivationMetadata(
bool implementsRegistryContract,
bool isAbstract,
Func<ICqrsHandlerRegistry>? factory)
{
/// <summary>
/// 获取目标类型是否实现了 <see cref="ICqrsHandlerRegistry" />。
/// </summary>
public bool ImplementsRegistryContract { get; } = implementsRegistryContract;
/// <summary>
/// 获取目标类型是否为抽象类型。
/// </summary>
public bool IsAbstract { get; } = isAbstract;
/// <summary>
/// 获取可用于实例化 registry 的工厂委托。
/// </summary>
public Func<ICqrsHandlerRegistry>? Factory { get; } = factory;
}
}

View File

@ -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 阻塞,避免影响后续代码类验证

View File

@ -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 内扩张为环境修复任务

View File

@ -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' }

View File

@ -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 模块契约、系统适配、配置对象和运行时装配边界 |

View File

@ -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<T>`
也就是说,新代码通常不需要再分别设计“命令总线”“查询总线”和另一套通知分发语义。
新代码通常不需要再分别设计“命令总线”“查询总线”和另一套通知分发语义。
## 注册处理器
## 处理器注册与生成器协作
在标准 `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<LoggingBehavior<,>>();
- 审计
- 重试或统一异常封装
旧的 `Mediator` 兼容别名入口已经移除;当前公开入口只有 `RegisterCqrsPipelineBehavior<TBehavior>()`
当前公开入口只有 `RegisterCqrsPipelineBehavior<TBehavior>()`
## 和旧 Command / Query 的关系
@ -157,15 +162,28 @@ RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
- 新路径
- `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<TInput, TResponse>``QueryBase<TInput, TResponse>``NotificationBase<TInput>``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`

View File

@ -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
<ItemGroup>
<PackageReference Include="GeWuYou.GFramework.Cqrs" Version="x.y.z" />
<PackageReference Include="GeWuYou.GFramework.Cqrs.Abstractions" Version="x.y.z" />
<PackageReference Include="GeWuYou.GFramework.Cqrs.SourceGenerators"
Version="x.y.z"
PrivateAssets="all"
ExcludeAssets="runtime" />
</ItemGroup>
```
运行时侧仍然按 `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)

View File

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