GFramework/docs/zh-CN/source-generators/cqrs-handler-registry-generator.md
gewuyou 66395739dc docs(governance): 收口公开文档治理口径
- 更新 AGENTS.md、DOCUMENTATION_STANDARDS.md 与 gframework-doc-refresh 规则,禁止在公开文档中暴露 inventory、覆盖基线与恢复点

- 修复 Core、Game、Ecs.Arch 与 abstractions 栏目中的 XML 覆盖表述,改为面向使用者的源码阅读入口

- 补充 contributor 页面 frontmatter,并统一 landing page、验证基线等内部术语为读者导向表达

- 更新 documentation-full-coverage-governance tracking 与 trace,记录 -batch-boot 75 基线、验证结果和下一步
2026-04-24 08:31:23 +08:00

4.6 KiB
Raw Blame History

title, description
title description
CQRS Handler Registry 生成器 为消费端程序集生成 CQRS handler registry并在需要时附带精确 reflection fallback 元数据。

CQRS Handler Registry 生成器

GFramework.Cqrs.SourceGenerators 会在编译期为当前业务程序集生成 ICqrsHandlerRegistry,让 GFramework.Cqrs runtime 在注册 handlers 时优先走静态注册表,而不是先扫描整个程序集。

它服务的是 Cqrs 家族,不是独立运行时:

  • 契约层:GeWuYou.GFramework.Cqrs.Abstractions
  • 默认 runtimeGeWuYou.GFramework.Cqrs
  • 编译期生成器:GeWuYou.GFramework.Cqrs.SourceGenerators

生成什么

当前生成器会分析消费端程序集中的:

  • IRequestHandler<,>
  • INotificationHandler<>
  • IStreamRequestHandler<,>

然后输出两类结果:

  1. 一个实现 ICqrsHandlerRegistry 的内部注册器类型
  2. 程序集级 CqrsHandlerRegistryAttribute

当某些 handler 不能被生成代码安全地直接引用时,还会补发:

  • 程序集级 CqrsReflectionFallbackAttribute

这意味着运行时会先使用生成注册器完成可静态表达的映射,再只对剩余类型做补扫,而不是退回整程序集盲扫。

最小接入路径

安装方式保持 runtime 包与生成器包版本一致,并把生成器作为编译期依赖引入:

<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 的标准入口注册程序集:

protected override void OnInitialize()
{
    RegisterCqrsHandlersFromAssembly(typeof(GameArchitecture).Assembly);
}

如果你的 handlers 分布在多个业务程序集里,则改用:

RegisterCqrsHandlersFromAssemblies(
[
    typeof(InventoryCqrsMarker).Assembly,
    typeof(BattleCqrsMarker).Assembly
]);

文档示例统一用 marker 类型承载程序集引用。框架本身不要求固定目录或固定命名,但团队实践里可以把这类空 marker 集中放在每个业务程序集自己的 Application/Markers 或等价目录,并采用 InventoryCqrsMarker 这类能直接看出来源 的名字,避免多人协作时拿无关业务类型充当程序集定位锚点。

运行时如何消费生成结果

Cqrs runtime 当前的注册顺序是:

  1. 先读取程序集上的 CqrsHandlerRegistryAttribute
  2. 优先激活生成的 ICqrsHandlerRegistry
  3. 若生成元数据损坏、registry 不可激活,记录告警并回退到反射路径
  4. 若存在 CqrsReflectionFallbackAttribute,只补扫剩余 handler
  5. 同一程序集按稳定键去重,避免重复注册

这个行为由 GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.csGFramework.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 合同不足以安全承载这轮生成结果”。

源码与 API 阅读入口

如果你要核对生成器对外暴露的契约,优先看这些类型:

  • GFramework.Cqrs.ICqrsHandlerRegistry
  • GFramework.Cqrs.CqrsHandlerRegistryAttribute
  • GFramework.Cqrs.CqrsReflectionFallbackAttribute
  • GFramework.Cqrs.SourceGenerators.Cqrs.CqrsHandlerRegistryGenerator

模块族入口见: