GeWuYou a79f02c987 docs(api): 添加 Core API 参考文档与事件系统接口文档
- 新增 Core API 参考文档,涵盖架构与模块、数据模型与系统、命令与查询等核心组件
- 添加事件系统接口详细文档,包括 IEvent、IEventBus、IUnRegister 等接口说明
- 提供完整的 API 使用示例路径、最佳实践与性能建议
- 包含架构图、依赖关系图与故障排查指南
- 添加测试用例参考与扩展方法说明
- [skip ci]
2026-01-21 23:45:10 +08:00

24 KiB
Raw Blame History

CQRS模式实现

**本文引用的文件** - [GFramework.Core\command\AbstractCommand.cs](file://GFramework.Core/command\AbstractCommand.cs) - [GFramework.Core\command\AbstractAsyncCommand.cs](file://GFramework.Core/command\AbstractAsyncCommand.cs) - [GFramework.Core\command\CommandBus.cs](file://GFramework.Core/command/CommandBus.cs) - [GFramework.Core\command\EmptyCommandInput.cs](file://GFramework.Core/command/EmptyCommandInput.cs) - [GFramework.Core\query\AbstractQuery.cs](file://GFramework.Core/query\AbstractQuery.cs) - [GFramework.Core\query\AbstractAsyncQuery.cs](file://GFramework.Core/query\AbstractAsyncQuery.cs) - [GFramework.Core\query\QueryBus.cs](file://GFramework.Core/query/QueryBus.cs) - [GFramework.Core\query\AsyncQueryBus.cs](file://GFramework.Core/query/AsyncQueryBus.cs) - [GFramework.Core\query\EmptyQueryInput.cs](file://GFramework.Core/query/EmptyQueryInput.cs) - [GFramework.Core.Abstractions\command\ICommand.cs](file://GFramework.Core.Abstractions/command/ICommand.cs) - [GFramework.Core.Abstractions\command\IAsyncCommand.cs](file://GFramework.Core.Abstractions/command/IAsyncCommand.cs) - [GFramework.Core.Abstractions\command\ICommandBus.cs](file://GFramework.Core.Abstractions/command/ICommandBus.cs) - [GFramework.Core.Abstractions\query\IQuery.cs](file://GFramework.Core.Abstractions/query/IQuery.cs) - [GFramework.Core.Abstractions\query\IAsyncQuery.cs](file://GFramework.Core.Abstractions/query/IAsyncQuery.cs) - [GFramework.Core.Abstractions\query\IQueryBus.cs](file://GFramework.Core.Abstractions/query/IQueryBus.cs) - [GFramework.Core.Tests\command\CommandBusTests.cs](file://GFramework.Core.Tests/command/CommandBusTests.cs) - [GFramework.Core.Tests\query\QueryBusTests.cs](file://GFramework.Core.Tests/query/QueryBusTests.cs)

目录

  1. 引言
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能考量
  8. 故障排查指南
  9. 结论
  10. 附录:游戏开发实战示例

引言

本教程围绕 GFramework 的 CQRS命令查询职责分离模式实现系统讲解命令与查询的分离设计、架构优势、生命周期管理、输入输出模型、异步处理与错误处理机制。读者将学会如何基于框架提供的抽象类与总线接口快速构建命令类AbstractCommand、查询类AbstractQuery并通过 CommandBus 和 QueryBus 进行统一调度与执行。

项目结构

本节聚焦于 CQRS 相关的核心目录与文件组织方式:

  • 命令层:抽象命令、异步命令、命令总线、空命令输入
  • 查询层:抽象查询、异步查询、查询总线、异步查询总线、空查询输入
  • 抽象接口层:定义命令与查询的契约及总线接口
graph TB
subgraph "命令层"
AC["AbstractCommand.cs"]
AAC["AbstractAsyncCommand.cs"]
CB["CommandBus.cs"]
ECI["EmptyCommandInput.cs"]
end
subgraph "查询层"
AQ["AbstractQuery.cs"]
AAQ["AbstractAsyncQuery.cs"]
QB["QueryBus.cs"]
AQB["AsyncQueryBus.cs"]
EQI["EmptyQueryInput.cs"]
end
subgraph "抽象接口层"
IC["ICommand.cs"]
IAC["IAsyncCommand.cs"]
IQ["IQuery.cs"]
IAQ["IAsyncQuery.cs"]
ICB["ICommandBus.cs"]
IQB["IQueryBus.cs"]
end
AC --> IC
AAC --> IAC
CB --> ICB
AQ --> IQ
AAQ --> IAQ
QB --> IQB
AQB --> IAQ
ECI --> IC
EQI --> IQ

图表来源

章节来源

核心组件

  • 命令抽象层
    • AbstractCommand无返回值命令抽象基类封装输入与执行入口
    • AbstractCommand<TInput, TResult>:有返回值命令抽象基类
    • AbstractAsyncCommand / AbstractAsyncCommand<TInput, TResult>:异步命令抽象基类
    • EmptyCommandInput无输入命令的占位输入类型
  • 查询抽象层
    • AbstractQuery<TInput, TResult>:同步查询抽象基类
    • AbstractAsyncQuery<TInput, TResult>:异步查询抽象基类
    • EmptyQueryInput无输入查询的占位输入类型
  • 总线层
    • CommandBus统一命令发送与执行入口支持同步/异步、有/无返回值
    • QueryBus统一查询发送与执行入口
    • AsyncQueryBus异步查询总线
  • 抽象接口层
    • ICommand / ICommand / IAsyncCommand / IAsyncCommand
    • IQuery / IAsyncQuery
    • ICommandBus / IQueryBus

章节来源

架构总览

CQRS 将“写”(命令)与“读”(查询)分离,分别采用独立的模型与执行路径,提升可扩展性、安全性与性能。在 GFramework 中:

  • 命令通过 CommandBus 统一调度,支持同步/异步与返回值
  • 查询通过 QueryBus/AsyncQueryBus 统一调度,支持同步/异步
  • 输入输出模型通过泛型约束与 EmptyXxxInput 占位类型实现清晰的契约
sequenceDiagram
participant Caller as "调用方"
participant Bus as "CommandBus/QueryBus"
participant Cmd as "命令/查询实例"
participant Impl as "具体实现"
Caller->>Bus : "Send/SendAsync(...)"
alt "命令"
Bus->>Cmd : "Execute()/ExecuteAsync()"
Cmd->>Impl : "OnExecute()/OnExecuteAsync()"
Impl-->>Bus : "返回值或完成信号"
else "查询"
Bus->>Cmd : "Do()/DoAsync()"
Cmd->>Impl : "OnDo()/OnDoAsync()"
Impl-->>Bus : "返回结果"
end
Bus-->>Caller : "结果/完成"

图表来源

详细组件分析

命令抽象与生命周期

  • 设计要点
    • 通过 ContextAwareBase 提供上下文感知能力,便于访问架构、系统、模型、工具等资源
    • 同步命令ICommand/ICommand -> AbstractCommand -> CommandBus.Send
    • 异步命令IAsyncCommand/IAsyncCommand -> AbstractAsyncCommand -> CommandBus.SendAsync
    • 生命周期:从构造输入 -> 调度 -> 执行 -> 返回/完成
  • 输入输出模型
    • TInput 必须实现 ICommandInput确保输入契约一致
    • 无返回值命令仅触发副作用;有返回值命令返回 TResult
  • 错误处理
    • CommandBus 对 null 命令显式抛出异常,保证调用方及时发现错误
    • 异常传播遵循 .NET Task/Exception 语义,建议在上层统一捕获与记录
classDiagram
class ICommand {
+Execute()
}
class ICommand_TResult_ {
+Execute() TResult
}
class IAsyncCommand {
+ExecuteAsync() Task
}
class IAsyncCommand_TResult_ {
+ExecuteAsync() Task~TResult~
}
class AbstractCommand_TInput_ {
-input : TInput
+Execute()
#OnExecute(input)
}
class AbstractCommand_TInput_TResult_ {
-input : TInput
+Execute() TResult
#OnExecute(input) TResult
}
class AbstractAsyncCommand_TInput_ {
-input : TInput
+ExecuteAsync() Task
#OnExecuteAsync(input) Task
}
class AbstractAsyncCommand_TInput_TResult_ {
-input : TInput
+ExecuteAsync() Task~TResult~
#OnExecuteAsync(input) Task~TResult~
}
class CommandBus {
+Send(command)
+Send~TResult~(command) TResult
+SendAsync(command) Task
+SendAsync~TResult~(command) Task~TResult~
}
ICommand <|.. AbstractCommand_TInput_
ICommand_TResult_ <|.. AbstractCommand_TInput_TResult_
IAsyncCommand <|.. AbstractAsyncCommand_TInput_
IAsyncCommand_TResult_ <|.. AbstractAsyncCommand_TInput_TResult_
ICommand <|.. CommandBus
IAsyncCommand <|.. CommandBus

图表来源

章节来源

查询抽象与生命周期

  • 设计要点
    • 同步查询IQuery -> AbstractQuery -> QueryBus.Send
    • 异步查询IAsyncQuery -> AbstractAsyncQuery -> AsyncQueryBus.SendAsync
    • 生命周期:输入校验 -> 调度 -> 执行 -> 返回结果
  • 输入输出模型
    • TInput 实现 IQueryInputTResult 为只输出协变类型
    • EmptyQueryInput 用于无需输入的查询场景
  • 错误处理
    • QueryBus/AsyncQueryBus 对 null 查询显式抛出异常
    • 建议在应用层对查询异常进行分类处理(缓存未命中、数据不存在、权限不足等)
classDiagram
class IQuery_TResult_ {
+Do() TResult
}
class IAsyncQuery_TResult_ {
+DoAsync() Task~TResult~
}
class AbstractQuery_TInput_TResult_ {
-input : TInput
+Do() TResult
#OnDo(input) TResult
}
class AbstractAsyncQuery_TInput_TResult_ {
-input : TInput
+DoAsync() Task~TResult~
#OnDoAsync(input) Task~TResult~
}
class QueryBus {
+Send~TResult~(query) TResult
}
class AsyncQueryBus {
+SendAsync~TResult~(query) Task~TResult~
}
IQuery_TResult_ <|.. AbstractQuery_TInput_TResult_
IAsyncQuery_TResult_ <|.. AbstractAsyncQuery_TInput_TResult_
IQuery_TResult_ <|.. QueryBus
IAsyncQuery_TResult_ <|.. AsyncQueryBus

图表来源

章节来源

总线实现与调用流程

  • CommandBus
    • Send/Send同步执行命令
    • SendAsync/SendAsync异步执行命令
    • 参数校验:对 null 命令抛出异常
  • QueryBus/AsyncQueryBus
    • Send/SendAsync执行查询并返回结果
    • 参数校验:对 null 查询抛出异常
flowchart TD
Start(["开始"]) --> Choose["选择总线类型"]
Choose --> |命令| CB["CommandBus"]
Choose --> |查询| QB["QueryBus/AsyncQueryBus"]
CB --> CheckCmd["校验命令非空"]
CheckCmd --> ExecCmd["调用命令.Execute()/ExecuteAsync()"]
ExecCmd --> RetCmd["返回结果/完成"]
QB --> CheckQ["校验查询非空"]
CheckQ --> ExecQ["调用查询.Do()/DoAsync()"]
ExecQ --> RetQ["返回查询结果"]
RetCmd --> End(["结束"])
RetQ --> End

图表来源

章节来源

输入输出模型与空输入类型

  • 输入模型
    • 命令输入ICommandInputEmptyCommandInput 作为无参命令的占位
    • 查询输入IQueryInputEmptyQueryInput 作为无参查询的占位
  • 输出模型
    • 命令:可选返回值 TResult 或无返回值
    • 查询:只输出协变 TResult
  • 使用建议
    • 优先使用强类型输入,避免魔法数字与字符串
    • 对于无输入场景,使用 EmptyXxxInput 明确意图

章节来源

异步处理与错误处理

  • 异步处理
    • 命令AbstractAsyncCommand + CommandBus.SendAsync
    • 查询AbstractAsyncQuery + AsyncQueryBus.SendAsync
  • 错误处理
    • 总线层对 null 参数抛出异常,调用方可据此快速定位问题
    • 建议在应用层捕获并记录异常,区分业务异常与系统异常

章节来源

依赖关系分析

  • 组件耦合
    • 命令/查询实现依赖抽象接口,降低与具体总线的耦合
    • 总线仅依赖接口契约,便于替换与扩展
  • 外部依赖
    • System.Threading.Tasks异步
    • GFramework.Core.rule.ContextAwareBase上下文能力
  • 潜在风险
    • 避免在命令中直接访问查询总线,防止循环依赖
    • 查询不应产生副作用,保持幂等性
graph LR
AC["AbstractCommand"] --> IC["ICommand/ICommand<TResult>"]
AAC["AbstractAsyncCommand"] --> IAC["IAsyncCommand/IAsyncCommand<TResult>"]
AQ["AbstractQuery"] --> IQ["IQuery<TResult>"]
AAQ["AbstractAsyncQuery"] --> IAQ["IAsyncQuery<TResult>"]
CB["CommandBus"] --> IC
CB --> IAC
QB["QueryBus"] --> IQ
AQB["AsyncQueryBus"] --> IAQ

图表来源

章节来源

性能考量

  • 命令与查询分离带来的收益
    • 写入与读取路径解耦,便于针对读路径做缓存与索引优化
    • 可以独立扩展命令/查询的执行环境
  • 异步化
    • 使用异步命令/查询避免阻塞主线程,适合游戏开发中的网络/IO密集场景
  • 泛型与装箱
    • 通过泛型减少装箱,提高性能;合理使用 EmptyXxxInput 减少对象分配
  • 总线调用开销
    • 总线本身轻量,主要成本在具体实现;建议在高频路径中复用命令/查询实例

故障排查指南

  • 常见问题
    • 传入 null 命令/查询:总线层会抛出异常,检查调用方是否正确构造命令/查询
    • 无输入命令/查询:使用 EmptyXxxInput 明确意图,避免遗漏输入
    • 异步未等待:确保调用方 await SendAsync/DoAsync
  • 单元测试参考
    • 命令总线测试覆盖了同步/异步、有/无返回值、null 场景
    • 查询总线测试覆盖了同步查询、null 场景与多返回类型

章节来源

结论

GFramework 的 CQRS 实现以清晰的抽象与总线为核心,提供了简洁而强大的命令/查询执行框架。通过分离写入与读取、统一调度与生命周期管理、完善的输入输出模型与异步支持,开发者可以快速构建可维护、高性能的游戏系统。建议在实际项目中结合领域模型,将业务规则收敛到命令/查询内部,配合事件总线实现最终一致性。

附录:游戏开发实战示例

以下示例描述如何在游戏开发中使用 CQRS 模式,涵盖常见场景。为保护代码完整性,示例仅给出步骤与要点,不直接展示源码。

  • 玩家创建(命令)

    • 步骤
      • 定义命令输入:包含玩家名、初始属性等字段
      • 继承 AbstractCommand 或 AbstractAsyncCommand 创建 PlayerCreateCommand
      • 在 OnExecute/OnExecuteAsync 中完成持久化、事件发布与状态更新
      • 通过 CommandBus.Send/SendAsync 触发执行
    • 关键点
      • 输入校验与权限检查
      • 幂等性设计(避免重复创建)
      • 异常回滚与补偿
  • 数据查询(查询)

    • 步骤
      • 定义查询输入:如分页参数、筛选条件
      • 继承 AbstractQuery 或 AbstractAsyncQuery 创建 PlayerListQuery
      • 在 OnDo/OnDoAsync 中执行数据库/缓存查询
      • 通过 QueryBus.Send/SendAsync 获取结果
    • 关键点
      • 只读查询,避免副作用
      • 缓存策略与失效机制
      • 分页与排序的性能优化
  • 统计信息获取(查询)

    • 步骤
      • 定义统计查询输入:时间范围、维度等
      • 继承 AbstractQuery 创建 StatQuery
      • 在 OnDo 中聚合数据并返回结构化结果
    • 关键点
      • 使用异步查询处理大数据集
      • 结果模型与前端约定一致
  • 异步命令与事件驱动

    • 步骤
      • 使用 AbstractAsyncCommand 完成耗时操作(如网络请求)
      • 在命令完成后发布领域事件,供其他模块订阅
    • 关键点
      • 事件总线与命令总线解耦
      • 事务边界与最终一致性
  • 最佳实践

    • 命令/查询命名清晰,职责单一
    • 输入输出模型强类型化,避免魔法值
    • 异步化优先,避免阻塞主线程
    • 单元测试覆盖所有分支与异常路径