docs(core): 重写核心专题页文档

- 更新 architecture、context、lifecycle、command、query 与 cqrs 页面,使其对齐当前公开 API 与初始化语义
- 移除 Init、属性式总线、旧输入赋值示例和已移除的 Mediator 兼容入口等过时说明
- 补充 documentation-governance-and-refresh 主题的恢复点、验证结果与下一步专题页计划
This commit is contained in:
GeWuYou 2026-04-21 09:14:06 +08:00
parent dfeb40ba15
commit 60faf8eaff
8 changed files with 612 additions and 2567 deletions

View File

@ -7,20 +7,21 @@
## 当前恢复点
- 恢复点编号:`DOCUMENTATION-GOVERNANCE-REFRESH-RP-002`
- 当前阶段:`Phase 2`
- 恢复点编号:`DOCUMENTATION-GOVERNANCE-REFRESH-RP-003`
- 当前阶段:`Phase 3`
- 当前焦点:
- 已完成 `docs/zh-CN/core/index.md``docs/zh-CN/game/index.md`
`docs/zh-CN/source-generators/index.md` 的 landing page 重写
- 栏目入口已改为以模块定位、包关系、最小接入路径和继续阅读为主,不再沿用旧版失真教程结构
- 下一轮需要继续核对并重写 `docs/zh-CN/core/*``docs/zh-CN/game/*`
`docs/zh-CN/source-generators/*` 的专题页内容
- 已完成 `docs/zh-CN/core/architecture.md``context.md``lifecycle.md``command.md``query.md`
`cqrs.md` 的专题页重写
- `core` 关键专题页已改回当前 `Architecture``ArchitectureContext`、旧 Command/Query 兼容层与新 CQRS
runtime 的真实入口语义
- 下一轮需要继续推进 `docs/zh-CN/core/*` 余下专题页,以及 `docs/zh-CN/game/*`
`docs/zh-CN/source-generators/*` 的专题页核对
## 当前状态摘要
- 文档治理规则已收口到仓库规范README、站点入口与采用链路不再依赖旧文档自证
- 高优先级模块入口已补齐,栏目 landing page 已回到可作为默认导航入口的状态
- 当前主题仍是 active topic因为核心栏目下的专题页仍可能包含与实现漂移的旧内容
- 高优先级模块入口`core` 关键专题页已回到可作为默认导航入口的状态
- 当前主题仍是 active topic因为 `core` 其余专题页及 `game``source-generators` 栏目下仍可能包含与实现漂移的旧内容
## 当前活跃事实
@ -29,6 +30,8 @@
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
- `core``game``source-generators` 三个栏目入口页现在都以模块 README 与当前包拆分为准
- `docs` 站点构建已验证通过,修正了 VitePress 对 `docs/` 目录外相对链接的 dead-link 检查问题
- `core` 关键专题页已移除 `Init()`、属性式 `CommandBus` / `QueryBus`、旧 `Input` 赋值式示例和已移除的
`RegisterMediatorBehavior` 等过时说明
## 当前风险
@ -53,6 +56,7 @@
## 下一步
1. 先从 `docs/zh-CN/core/*` 开始,逐页核对架构、上下文、生命周期、命令、查询与 CQRS 的示例和术语
1. 继续核对 `docs/zh-CN/core/*` 余下专题页,优先处理 `events``property``state-management``coroutine`
`logging`
2. 再推进 `docs/zh-CN/game/*``docs/zh-CN/source-generators/*` 的专题页重写,优先处理仍引用旧安装方式或旧 API 的页面
3. 若专题页批量重写完成且验证通过,将本轮 landing page 收口和下一轮专题页修订过程迁入本 topic 的 `archive/`
3. 若专题页批量重写完成且验证通过,将本轮 `core` 专题页收口和后续修订过程迁入本 topic 的 `archive/`

View File

@ -62,3 +62,23 @@
- `documentation-governance-and-refresh` active tracking 已同步把风险缓解中的参考来源更新为
`ai-libs/` 下已验证参考实现
- 下一次专题页重写时,继续沿用同一表述,不再把特定参考项目名写入新的活跃文档入口
### 补充2026-04-21 Core 专题页收口RP-003
- 复核 `docs/zh-CN/core/architecture.md``context.md``lifecycle.md``command.md``query.md``cqrs.md`
后确认:这些页面仍大量保留旧 API 叙述,例如 `Init()`、属性式 `CommandBus` / `QueryBus`、旧 `Input`
赋值式命令/查询示例,以及已移除的 `RegisterMediatorBehavior`
- 对照 `Architecture``ArchitectureContext``IArchitectureContext``ContextAwareBase`、旧
`AbstractCommand` / `AbstractQuery` 基类和 `GFramework.Cqrs/README.md` 后,重写上述六个页面
- 新版专题页将结构统一为“当前角色、真实公开入口、最小示例、兼容边界、迁移方向”,避免继续复刻旧版大而全教程
- `core/context.md` 已明确把 `GameContext` 收束为兼容回退路径,而不是新代码的推荐接法
- `core/command.md``core/query.md` 已明确旧体系仍可用,但新功能应优先走 `GFramework.Cqrs`
- `core/cqrs.md` 已与当前 runtime / generator / handler 注册语义对齐,并明确 `RegisterCqrsPipelineBehavior<TBehavior>()`
是公开入口
- 执行 `cd docs && bun run build` 通过,说明本轮 `core` 专题页重写没有破坏文档站构建
### 下一步
1. 继续处理 `docs/zh-CN/core/events.md``property.md``state-management.md``coroutine.md``logging.md`
2. 保持同样的证据顺序:源码、`*.csproj`、模块 README、`ai-libs/` 参考实现
3. 完成下一批专题页重写后再次执行 `cd docs && bun run build`

View File

@ -1,239 +1,147 @@
# Architecture 架构详解
# Architecture
> 深入了解 GFramework 的核心架构设计和实现
`Architecture``GFramework.Core` 的运行时入口。它负责三件事:
## 目录
- 组织初始化与销毁阶段
- 接入模型、系统、工具和模块
- 暴露 `ArchitectureContext` 作为统一上下文入口
- [概述](#概述)
- [架构设计](#架构设计)
- [生命周期管理](#生命周期管理)
- [组件注册](#组件注册)
- [模块系统](#模块系统)
- [最佳实践](#最佳实践)
- [API 参考](#api-参考)
当前版本的 `Architecture` 已经是协调器外观。对外仍保留稳定的注册与生命周期 API但内部职责已经拆给专门协作者处理。
## 概述
## 你真正会用到的公开入口
Architecture 是 GFramework 的核心类,负责管理整个应用的生命周期、组件注册和模块管理。从 v1.1.0 开始,Architecture
采用模块化设计,将职责分离到专门的协作者中。
最常见的成员只有这些:
> 命名约定:
> - `ArchitectureServices` 是公开的基础服务入口,负责容器、事件总线、命令执行器、查询执行器和服务模块管理
> - `ArchitectureComponentRegistry` 是内部组件注册器,专门负责 System / Model / Utility 的注册与生命周期接入
> - 两者不是同一层职责,不要混用
- `OnInitialize()`
- 子类唯一必须实现的入口,用来注册模型、系统、工具、模块和额外的 CQRS 行为
- `RegisterModel(...)` / `RegisterSystem(...)` / `RegisterUtility(...)`
- 注册运行时组件
- `InstallModule(...)`
- 安装实现了 `IArchitectureModule` 的模块
- `RegisterLifecycleHook(...)`
- 注册阶段钩子
- `RegisterCqrsPipelineBehavior<TBehavior>()`
- 注册 CQRS pipeline 行为
- `RegisterCqrsHandlersFromAssembly(...)` / `RegisterCqrsHandlersFromAssemblies(...)`
- 显式接入其他程序集中的 CQRS handlers
- `InitializeAsync()` / `WaitUntilReadyAsync()`
- 启动架构并等待进入 `Ready`
- `DestroyAsync()`
- 逆序销毁所有已接入组件
### 设计目标
- **单一职责**: 每个管理器只负责一个明确的功能
- **类型安全**: 基于泛型的组件获取和注册
- **生命周期管理**: 自动的初始化和销毁机制
- **可扩展性**: 支持模块和钩子扩展
- **向后兼容**: 保持公共 API 稳定
### 核心组件
```
Architecture (核心协调器)
├── ArchitectureBootstrapper (初始化基础设施编排)
├── ArchitectureLifecycle (生命周期管理)
├── ArchitectureComponentRegistry (组件注册)
└── ArchitectureModules (模块管理)
```
## 架构设计
### 设计模式
Architecture 采用以下设计模式:
1. **组合模式 (Composition)**: Architecture 组合多个内部协作者
2. **委托模式 (Delegation)**: 方法调用委托给专门的管理器
3. **协调器模式 (Coordinator)**: Architecture 作为协调器统一对外接口
### 类图
```
┌─────────────────────────────────────────────────────┐
│ Architecture │
│ - _bootstrapper: ArchitectureBootstrapper │
│ - _lifecycle: ArchitectureLifecycle │
│ - _componentRegistry: ArchitectureComponentRegistry│
│ - _modules: ArchitectureModules │
│ - _logger: ILogger │
│ │
│ + RegisterSystem<T>() │
│ + RegisterModel<T>() │
│ + RegisterUtility<T>() │
│ + InstallModule() │
│ + InitializeAsync() │
│ + DestroyAsync() │
│ + event PhaseChanged │
└─────────────────────────────────────────────────────┘
│ │ │ │
│ │ │ │
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ ┌──────────────┐
│ Bootstrapper │ │ Lifecycle │ │ComponentReg. │ │ Modules │
│ │ │ │ │ │ │ │
│ - 环境初始化 │ │ - 阶段管理 │ │ - System 注册│ │ - 模块安装 │
│ - 服务准备 │ │ - 钩子管理 │ │ - Model 注册 │ │ - 行为注册 │
│ - 上下文绑定 │ │ - 组件初始化 │ │ - Utility 注册│ │ │
│ - 容器冻结 │ │ - 就绪/销毁协调 │ │ - 生命周期接入│ │ │
└──────────────┘ └──────────────────┘ └──────────────┘ └──────────────┘
```
### 构造函数初始化
从 v1.1.0 开始,所有管理器在构造函数中初始化:
## 最小示例
```csharp
protected Architecture(
IArchitectureConfiguration? configuration = null,
IEnvironment? environment = null,
IArchitectureServices? services = null,
IArchitectureContext? context = null)
{
var resolvedConfiguration = configuration ?? new ArchitectureConfiguration();
var resolvedEnvironment = environment ?? new DefaultEnvironment();
var resolvedServices = services ?? new ArchitectureServices();
_context = context;
using GFramework.Core.Architectures;
// 初始化 Logger
LoggerFactoryResolver.Provider = resolvedConfiguration.LoggerProperties.LoggerFactoryProvider;
_logger = LoggerFactoryResolver.Provider.CreateLogger(GetType().Name);
// 初始化协作者
_bootstrapper = new ArchitectureBootstrapper(GetType(), resolvedEnvironment, resolvedServices, _logger);
_lifecycle = new ArchitectureLifecycle(this, resolvedConfiguration, resolvedServices, _logger);
_componentRegistry = new ArchitectureComponentRegistry(this, resolvedConfiguration, resolvedServices, _lifecycle, _logger);
_modules = new ArchitectureModules(this, resolvedServices, _logger);
}
```
**优势**:
- 消除 `null!` 断言,提高代码安全性
- 对象在构造后立即可用
- 符合"构造即完整"原则
- 可以在 InitializeAsync 之前访问事件
## 生命周期管理
### 架构阶段
Architecture 定义了 11 个生命周期阶段:
| 阶段 | 说明 | 触发时机 |
|------------------------|--------------|------------------|
| `None` | 初始状态 | 构造函数完成后 |
| `BeforeUtilityInit` | Utility 初始化前 | 开始初始化 Utility |
| `AfterUtilityInit` | Utility 初始化后 | 所有 Utility 初始化完成 |
| `BeforeModelInit` | Model 初始化前 | 开始初始化 Model |
| `AfterModelInit` | Model 初始化后 | 所有 Model 初始化完成 |
| `BeforeSystemInit` | System 初始化前 | 开始初始化 System |
| `AfterSystemInit` | System 初始化后 | 所有 System 初始化完成 |
| `Ready` | 就绪状态 | 所有组件初始化完成 |
| `Destroying` | 销毁中 | 开始销毁 |
| `Destroyed` | 已销毁 | 销毁完成 |
| `FailedInitialization` | 初始化失败 | 初始化过程中发生异常 |
### 阶段转换
```
正常流程:
None → BeforeUtilityInit → AfterUtilityInit → BeforeModelInit → AfterModelInit
→ BeforeSystemInit → AfterSystemInit → Ready → Destroying → Destroyed
异常流程:
Any → FailedInitialization
```
### 阶段事件
可以通过 `PhaseChanged` 事件监听阶段变化:
```csharp
public class MyArchitecture : Architecture
public sealed class GameArchitecture : Architecture
{
protected override void OnInitialize()
{
// 监听阶段变化
PhaseChanged += phase =>
{
Console.WriteLine($"Phase changed to: {phase}");
};
RegisterModel(new PlayerModel());
RegisterSystem(new CombatSystem());
RegisterUtility(new SaveUtility());
}
}
```
### 生命周期钩子
实现 `IArchitectureLifecycleHook` 接口可以在阶段变化时执行自定义逻辑:
启动方式:
```csharp
public class MyLifecycleHook : IArchitectureLifecycleHook
var architecture = new GameArchitecture();
await architecture.InitializeAsync();
await architecture.WaitUntilReadyAsync();
```
## 初始化时机
当前版本不再使用旧文档里的 `Init()` 入口。注册逻辑必须写在:
```csharp
protected override void OnInitialize()
{
}
```
框架会在 `InitializeAsync()` 内完成:
1. 基础设施准备
2. 创建并绑定 `ArchitectureContext`
3. 调用用户的 `OnInitialize()`
4. 按阶段初始化 `Utility -> Model -> System`
5. 进入 `Ready`
如果你还看到旧示例里写 `protected override void Init()`,那就是过时内容。
## 组件注册顺序
`Architecture` 仍然维持清晰的组件边界:
- `Model`
- 承载状态
- `System`
- 承载业务流程
- `Utility`
- 承载无状态或基础设施型能力
初始化顺序固定为:
1. `Utility`
2. `Model`
3. `System`
销毁时会按逆序处理,并优先调用异步销毁接口。
## 模块与 CQRS 接入
如果你的功能以模块形式组织,优先通过 `InstallModule(...)` 接入,而不是把所有注册逻辑都堆进一个超大的 `OnInitialize()`
如果 handlers 不只在当前架构程序集里,需要显式追加程序集:
```csharp
protected override void OnInitialize()
{
RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
RegisterCqrsHandlersFromAssemblies(
[
typeof(InventoryCqrsMarker).Assembly,
typeof(BattleCqrsMarker).Assembly
]);
}
```
默认运行时会优先尝试消费端程序集上的生成注册表;缺失或不适用时回退到反射扫描。
## 阶段与钩子
`Architecture` 公开:
- `CurrentPhase`
- `IsReady`
- `PhaseChanged`
- `RegisterLifecycleHook(...)`
如果你需要在 `Ready``Destroying` 等阶段执行横切逻辑,比起把这类逻辑塞进某个具体 `System`,更适合单独实现
`IArchitectureLifecycleHook`
```csharp
public sealed class MetricsHook : IArchitectureLifecycleHook
{
public void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
{
switch (phase)
if (phase == ArchitecturePhase.Ready)
{
case ArchitecturePhase.Ready:
Console.WriteLine("Architecture is ready!");
break;
case ArchitecturePhase.Destroying:
Console.WriteLine("Architecture is being destroyed!");
break;
Console.WriteLine("Architecture ready.");
}
}
}
// 注册钩子
architecture.RegisterLifecycleHook(new MyLifecycleHook());
```
### 初始化流程
## 什么时候看别的页面
```
1. 创建 Architecture 实例
└─> 构造函数初始化管理器
2. 调用 InitializeAsync() 或 Initialize()
├─> ArchitectureBootstrapper 准备基础设施
│ ├─> 初始化环境 (Environment.Initialize())
│ ├─> 注册内置服务模块
│ ├─> 初始化架构上下文并绑定 GameContext
│ ├─> 执行服务钩子
│ └─> 初始化服务模块
├─> 调用 OnInitialize() (用户注册组件)
├─> 初始化所有组件
│ ├─> BeforeUtilityInit → 初始化 Utility → AfterUtilityInit
│ ├─> BeforeModelInit → 初始化 Model → AfterModelInit
│ └─> BeforeSystemInit → 初始化 System → AfterSystemInit
├─> CompleteInitialization() 冻结 IoC 容器
└─> 进入 Ready 阶段
3. 等待就绪 (可选)
└─> await architecture.WaitUntilReadyAsync()
```
### 销毁流程
```
1. 调用 DestroyAsync() 或 Destroy()
├─> 检查当前阶段 (如果是 None 或已销毁则直接返回)
├─> 进入 Destroying 阶段
├─> 逆序销毁所有组件
│ ├─> 优先调用 IAsyncDestroyable.DestroyAsync()
│ └─> 否则调用 IDestroyable.Destroy()
├─> 销毁服务模块
├─> 进入 Destroyed 阶段
└─> 清空 IoC 容器
```
---
**版本**: 1.1.0
**更新日期**: 2026-03-17
**相关文档**:
- [核心框架概述](./index.md)
- 想看上下文 API转到 [context](./context.md)
- 想看阶段和销毁语义:转到 [lifecycle](./lifecycle.md)
- 想看旧命令 / 查询兼容层:转到 [command](./command.md) 和 [query](./query.md)
- 想看推荐的新请求模型:转到 [cqrs](./cqrs.md)

View File

@ -1,51 +1,29 @@
# Command 包使用说明
# Command
## 概述
本页只说明 `GFramework.Core.Command` 里的旧命令体系。
Command 包实现了命令模式Command Pattern用于封装用户操作和业务逻辑。通过命令模式可以将请求封装为对象实现操作的参数化、队列化、日志记录、撤销等功能
它仍然被保留,用来兼容存量代码;但如果你在写新功能,优先使用 [cqrs](./cqrs.md) 里的新请求模型
命令系统是 GFramework CQRS 架构的重要组成部分,与事件系统和查询系统协同工作,实现完整的业务逻辑处理流程。
## 当前仍然可用的基类
## 核心接口
旧命令体系当前最常见的三个基类是:
### ICommand
- `AbstractCommand`
- 无输入、无返回值
- `AbstractCommand<TInput>`
- 有输入、无返回值
- `AbstractCommand<TInput, TResult>`
- 有输入、有返回值
无返回值命令接口,定义了命令的基本契约。
注意一个和旧文档不同的点:泛型命令现在通过构造函数接收输入,而不是依赖 `Input` 可写属性
**核心方法:**
## 无输入命令
```csharp
void Execute(); // 执行命令
```
using GFramework.Core.Command;
using GFramework.Core.Extensions;
### ICommand`<TResult>`
带返回值的命令接口,用于需要返回执行结果的命令。
**核心方法:**
```csharp
TResult Execute(); // 执行命令并返回结果
```
## 核心类
### AbstractCommand
无返回值命令的抽象基类,提供了命令的基础实现。它继承自 ContextAwareBase具有上下文感知能力。
**核心方法:**
```csharp
void ICommand.Execute(); // 实现 ICommand 接口
protected abstract void OnExecute(); // 抽象执行方法,由子类实现
```
**使用示例:**
```csharp
// 定义一个无返回值的基础命令
public class SimpleCommand : AbstractCommand
public sealed class RestoreHealthCommand : AbstractCommand
{
protected override void OnExecute()
{
@ -54,422 +32,93 @@ public class SimpleCommand : AbstractCommand
this.SendEvent(new PlayerHealthRestoredEvent());
}
}
// 使用命令
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
[ContextAware]
public partial class GameController : IController
{
public void OnRestoreHealthButtonClicked()
{
this.SendCommand(new SimpleCommand());
}
}
```
### AbstractCommand`<TResult>`
无输入参数但带返回值的命令基类。
**核心方法:**
发送方式:
```csharp
TResult ICommand<TResult>.Execute(); // 实现 ICommand<TResult> 接口
protected abstract TResult OnExecute(); // 抽象执行方法,由子类实现
this.SendCommand(new RestoreHealthCommand());
```
**使用示例:**
## 带输入命令
旧命令输入类型现在直接复用 CQRS 抽象层里的 `ICommandInput`
```csharp
// 定义一个无输入但有返回值的命令
public class GetPlayerHealthQuery : AbstractCommand<int>
using GFramework.Core.Command;
using GFramework.Core.Extensions;
using GFramework.Cqrs.Abstractions.Cqrs.Command;
public sealed record DamagePlayerInput(int Amount) : ICommandInput;
public sealed class DamagePlayerCommand(DamagePlayerInput input)
: AbstractCommand<DamagePlayerInput>(input)
{
protected override int OnExecute()
protected override void OnExecute(DamagePlayerInput input)
{
var playerModel = this.GetModel<PlayerModel>();
return playerModel.Health.Value;
}
}
// 使用命令
public class UISystem : AbstractSystem
{
protected override void OnInit()
{
this.RegisterEvent<UpdateUIEvent>(OnUpdateUI);
}
private void OnUpdateUI(UpdateUIEvent e)
{
var health = this.SendCommand(new GetPlayerHealthQuery());
Console.WriteLine($"Player health: {health}");
playerModel.Health.Value -= input.Amount;
}
}
```
## 命令的生命周期
1. **创建命令**:实例化命令对象,传入必要的参数
2. **执行命令**:调用 `Execute()` 方法,内部委托给 `OnExecute()`
3. **返回结果**:对于带返回值的命令,返回执行结果
4. **命令销毁**:命令执行完毕后可以被垃圾回收
**注意事项:**
- 命令应该是无状态的,执行完即可丢弃
- 避免在命令中保存长期引用
- 命令执行应该是原子操作
### 与 Store 配合使用
当某个 Model 内部使用 `Store<TState>` 管理复杂聚合状态时Command 依然是推荐的写入口。
发送方式:
```csharp
public sealed class DamagePlayerCommand(int amount) : AbstractCommand
{
protected override void OnExecute()
{
var model = this.GetModel<PlayerPanelModel>();
model.Store.Dispatch(new DamagePlayerAction(amount));
}
}
this.SendCommand(new DamagePlayerCommand(new DamagePlayerInput(10)));
```
这样可以保持现有职责边界不变:
- Controller 发送命令
- Command 执行操作
- Model 承载状态
- Store 负责统一归约状态变化
完整示例见 [`state-management`](./state-management)。
## CommandBus - 命令总线
### 功能说明
`CommandBus` 是命令执行的核心组件,负责发送和执行命令。
**主要方法:**
## 带返回值命令
```csharp
void Send(ICommand command); // 发送无返回值命令
TResult Send<TResult>(ICommand<TResult> command); // 发送带返回值命令
using GFramework.Core.Command;
using GFramework.Core.Extensions;
using GFramework.Cqrs.Abstractions.Cqrs.Command;
public sealed record GetGoldRewardInput(int EnemyLevel) : ICommandInput;
public sealed class GetGoldRewardCommand(GetGoldRewardInput input)
: AbstractCommand<GetGoldRewardInput, int>(input)
{
protected override int OnExecute(GetGoldRewardInput input)
{
return input.EnemyLevel * 10;
}
}
```
**特点:**
- 统一的命令执行入口
- 支持同步命令执行
- 与架构上下文集成
### 使用示例
```csharp
// 通过架构获取命令总线
var commandBus = architecture.Context.CommandBus;
// 发送无返回值命令
commandBus.Send(new StartGameCommand(1, "Player1"));
// 发送带返回值命令
var damage = commandBus.Send(new CalculateDamageCommand(100, 50));
var reward = this.SendCommand(new GetGoldRewardCommand(new GetGoldRewardInput(3)));
```
## 命令基类变体
## 发送入口
框架提供了多种命令基类以满足不同需求
旧命令由 `IArchitectureContext` 的兼容入口执行:
### AbstractCommand`<TInput>`
- `SendCommand(ICommand)`
- `SendCommand<TResult>(ICommand<TResult>)`
- `SendCommandAsync(IAsyncCommand)`
- `SendCommandAsync<TResult>(IAsyncCommand<TResult>)`
带输入参数的无返回值命令类。通过 `ICommandInput` 接口传递参数。
**核心方法:**
`IContextAware` 对象内,通常直接通过扩展使用:
```csharp
void ICommand.Execute(); // 实现 ICommand 接口
protected abstract void OnExecute(TInput input); // 抽象执行方法,接收输入参数
using GFramework.Core.Extensions;
```
**使用示例:**
## 什么时候还应该用旧命令
```csharp
// 定义输入对象
public class StartGameInput : ICommandInput
{
public int LevelId { get; set; }
public string PlayerName { get; set; }
}
- 你在维护既有 `Core.Command` 代码
- 你的调用链已经依赖旧 `CommandExecutor`
- 当前改动目标是局部修复,不值得同时做 CQRS 迁移
// 定义命令
public class StartGameCommand : AbstractCommand<StartGameInput>
{
protected override void OnExecute(StartGameInput input)
{
var playerModel = this.GetModel<PlayerModel>();
var gameModel = this.GetModel<GameModel>();
## 什么时候该切到 CQRS
playerModel.PlayerName.Value = input.PlayerName;
gameModel.CurrentLevel.Value = input.LevelId;
gameModel.GameState.Value = GameState.Playing;
下面这些场景更适合新 CQRS runtime
this.SendEvent(new GameStartedEvent());
}
}
- 需要 request / notification / stream 的统一模型
- 需要 pipeline behaviors
- 需要 handler registry 生成器
- 你正在写新的业务模块,而不是维护历史命令代码
// 使用命令
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
[ContextAware]
public partial class GameController : IController
{
public void OnStartButtonClicked()
{
var input = new StartGameInput { LevelId = 1, PlayerName = "Player1" };
this.SendCommand(new StartGameCommand { Input = input });
}
}
```
### AbstractCommand`<TInput, TResult>`
既带输入参数又带返回值的命令类。
**核心方法:**
```csharp
TResult ICommand<TResult>.Execute(); // 实现 ICommand<TResult> 接口
protected abstract TResult OnExecute(TInput input); // 抽象执行方法,接收输入参数
```
**使用示例:**
```csharp
// 定义输入对象
public class CalculateDamageInput : ICommandInput
{
public int AttackerAttackPower { get; set; }
public int DefenderDefense { get; set; }
}
// 定义命令
public class CalculateDamageCommand : AbstractCommand<CalculateDamageInput, int>
{
protected override int OnExecute(CalculateDamageInput input)
{
var config = this.GetModel<GameConfigModel>();
var baseDamage = input.AttackerAttackPower - input.DefenderDefense;
var finalDamage = Math.Max(1, baseDamage * config.DamageMultiplier);
return (int)finalDamage;
}
}
// 使用命令
public class CombatSystem : AbstractSystem
{
protected override void OnInit() { }
public void Attack(Character attacker, Character defender)
{
var input = new CalculateDamageInput
{
AttackerAttackPower = attacker.AttackPower,
DefenderDefense = defender.Defense
};
var damage = this.SendCommand(new CalculateDamageCommand { Input = input });
defender.Health -= damage;
this.SendEvent(new DamageDealtEvent(attacker, defender, damage));
}
}
```
### AbstractAsyncCommand`<TInput>`
支持异步执行的带输入参数的无返回值命令基类。
**核心方法:**
```csharp
Task IAsyncCommand.ExecuteAsync(); // 实现异步命令接口
protected abstract Task OnExecuteAsync(TInput input); // 抽象异步执行方法
```
### AbstractAsyncCommand`<TInput, TResult>`
支持异步执行的既带输入参数又带返回值的命令基类。
**核心方法:**
```csharp
Task<TResult> IAsyncCommand<TResult>.ExecuteAsync(); // 实现异步命令接口
protected abstract Task<TResult> OnExecuteAsync(TInput input); // 抽象异步执行方法
```
**使用示例:**
```csharp
// 定义输入对象
public class LoadSaveDataInput : ICommandInput
{
public string SaveSlot { get; set; }
}
// 定义异步命令
public class LoadSaveDataCommand : AbstractAsyncCommand<LoadSaveDataInput, SaveData>
{
protected override async Task<SaveData> OnExecuteAsync(LoadSaveDataInput input)
{
var storage = this.GetUtility<IStorageUtility>();
return await storage.LoadSaveDataAsync(input.SaveSlot);
}
}
// 使用异步命令
public class SaveSystem : AbstractSystem
{
protected override void OnInit()
{
this.RegisterEvent<LoadGameRequestEvent>(OnLoadGameRequest);
}
private async void OnLoadGameRequest(LoadGameRequestEvent e)
{
var input = new LoadSaveDataInput { SaveSlot = e.SaveSlot };
var saveData = await this.SendCommandAsync(new LoadSaveDataCommand { Input = input });
if (saveData != null)
{
this.SendEvent(new GameLoadedEvent { SaveData = saveData });
}
}
}
```
## 命令处理器执行
所有发送给命令总线的命令最终都会通过 `CommandExecutor` 来执行:
```csharp
public class CommandExecutor
{
public static void Execute(ICommand command)
{
command.Execute();
}
public static TResult Execute<TResult>(ICommand<TResult> command)
{
return command.Execute();
}
}
```
**特点:**
- 提供统一的命令执行机制
- 支持同步和异步命令执行
- 可以扩展添加中间件逻辑
## 使用场景
### 1. 用户交互操作
```csharp
public class SaveGameCommand : AbstractCommand
{
private readonly string _saveSlot;
public SaveGameCommand(string saveSlot)
{
_saveSlot = saveSlot;
}
protected override void OnExecute()
{
var saveSystem = this.GetSystem<SaveSystem>();
var playerModel = this.GetModel<PlayerModel>();
saveSystem.SavePlayerData(playerModel, _saveSlot);
this.SendEvent(new GameSavedEvent(_saveSlot));
}
}
```
### 2. 业务流程控制
```csharp
public class LoadLevelCommand : AbstractCommand
{
private readonly int _levelId;
public LoadLevelCommand(int levelId)
{
_levelId = levelId;
}
protected override void OnExecute()
{
var levelSystem = this.GetSystem<LevelSystem>();
var uiSystem = this.GetSystem<UISystem>();
// 显示加载界面
uiSystem.ShowLoadingScreen();
// 加载关卡
levelSystem.LoadLevel(_levelId);
// 发送事件
this.SendEvent(new LevelLoadedEvent(_levelId));
}
}
```
## 最佳实践
1. **保持命令原子性**:一个命令应该完成一个完整的业务操作
2. **命令无状态**:命令不应该保存长期状态,执行完即可丢弃
3. **参数通过构造函数传递**:命令需要的参数应在创建时传入
4. **避免命令嵌套**:命令内部尽量不要发送其他命令,使用事件通信
5. **合理使用返回值**:只在确实需要返回结果时使用带返回值的命令
6. **命令命名规范**:使用动词+名词形式,如 `StartGameCommand``SavePlayerCommand`
7. **单一职责原则**:每个命令只负责一个特定的业务操作
8. **使用异步命令**:对于需要长时间执行的操作,使用异步命令避免阻塞
9. **命令验证**:在命令执行前验证输入参数的有效性
10. **错误处理**:在命令中适当处理异常情况
## 命令模式优势
### 1. 可扩展性
- 命令可以被序列化和存储
- 支持命令队列和批处理
- 便于实现撤销/重做功能
### 2. 可测试性
- 命令逻辑独立,易于单元测试
- 可以模拟命令执行结果
- 支持行为驱动开发
### 3. 可维护性
- 业务逻辑集中管理
- 降低组件间耦合度
- 便于重构和扩展
## 相关包
- [`architecture`](./architecture.md) - 架构核心,负责命令的分发和执行
- [`extensions`](./extensions.md) - 提供 `SendCommand()` 扩展方法
- [`query`](./query.md) - 查询模式,用于数据查询
- [`events`](./events.md) - 事件系统,命令执行后的通知机制
- [`system`](./system.md) - 业务系统,命令的主要执行者
- [`model`](./model.md) - 数据模型,命令操作的数据
---
**许可证**Apache 2.0
迁移后常见写法见:[cqrs](./cqrs.md)

View File

@ -1,490 +1,163 @@
# Context 上下文指南
# Context
## 概述
`IArchitectureContext` 是框架的统一上下文入口。
Context上下文是 GFramework 中的核心概念,提供了对架构服务的统一访问入口。通过 Context组件可以访问事件总线、命令总线、查询总线、IoC
容器等核心服务。
当前版本的上下文不再以“公开属性总线”作为主要模型,而是以一组明确的方法同时承载:
## 核心接口
- 组件获取
- 事件系统
- 旧 Command / Query 兼容入口
- 新 CQRS runtime 入口
### IArchitectureContext
默认实现类型是 `ArchitectureContext`
架构上下文接口,定义了对架构服务的访问契约。
## 先记住一个事实
**核心属性:**
如果你还在找旧文档里的这些属性:
- `CommandBus`
- `QueryBus`
- `EventBus`
- `Container`
那说明你看到的是旧写法。当前推荐入口是方法,不是这些属性式总线。
## 组件访问
`IArchitectureContext` 直接提供按类型获取组件的方法:
```csharp
IEventBus EventBus { get; } // 事件总线
ICommandBus CommandBus { get; } // 命令总线
IQueryBus QueryBus { get; } // 查询总线
IIocContainer Container { get; } // IoC 容器
IEnvironment Environment { get; } // 环境配置
IArchitectureConfiguration Configuration { get; } // 架构配置
ILogger Logger { get; } // 日志系统
```
## 核心类
### ArchitectureContext
架构上下文的完整实现。
**使用示例:**
```csharp
// 通过架构获取上下文
var context = architecture.Context;
// 访问各个服务
var eventBus = context.EventBus;
var commandBus = context.CommandBus;
var queryBus = context.QueryBus;
var container = context.Container;
var environment = context.Environment;
var logger = context.Logger;
var model = context.GetModel<PlayerModel>();
var system = context.GetSystem<CombatSystem>();
var utility = context.GetUtility<SaveUtility>();
var service = context.GetService<IMyService>();
```
### GameContext
也支持批量获取和按优先级获取:
游戏上下文类,管理架构类型与上下文实例的映射关系。
- `GetModels<T>()`
- `GetSystems<T>()`
- `GetUtilities<T>()`
- `GetServices<T>()`
- `GetModelsByPriority<T>()`
- `GetSystemsByPriority<T>()`
- `GetUtilitiesByPriority<T>()`
- `GetServicesByPriority<T>()`
**核心方法:**
## 在 `IContextAware` 对象里怎么用
大多数业务代码不会手动把 `architecture.Context` 传来传去,而是通过 `IContextAware` 扩展方法访问上下文:
```csharp
// 绑定架构类型到上下文
static void Bind<TArchitecture>(IArchitectureContext context)
where TArchitecture : IArchitecture;
using GFramework.Core.Extensions;
// 获取架构类型对应的上下文
static IArchitectureContext GetContext<TArchitecture>()
where TArchitecture : IArchitecture;
// 解绑架构类型
static void Unbind<TArchitecture>()
where TArchitecture : IArchitecture;
```
## 在组件中使用 Context
### 在 Model 中使用
```csharp
public class PlayerModel : AbstractModel
{
public BindableProperty<int> Health { get; } = new(100);
protected override void OnInit()
{
// 通过 Context 访问事件总线
var context = this.GetContext();
var eventBus = context.EventBus;
// 监听生命值变化
Health.Register(hp =>
{
if (hp <= 0)
{
// 发送事件
eventBus.Send(new PlayerDiedEvent());
}
});
}
}
```
### 在 System 中使用
```csharp
public class CombatSystem : AbstractSystem
{
protected override void OnInit()
{
// 通过 Context 访问各个服务
var context = this.GetContext();
var eventBus = context.EventBus;
var commandBus = context.CommandBus;
var container = context.Container;
// 注册事件监听
eventBus.Register<EnemyAttackEvent>(OnEnemyAttack);
}
private void OnEnemyAttack(EnemyAttackEvent e)
{
var context = this.GetContext();
var playerModel = context.Container.Get<PlayerModel>();
// 处理伤害
playerModel.Health.Value -= e.Damage;
}
}
```
### 在 Command 中使用
```csharp
public class StartGameCommand : AbstractCommand
public sealed class DamagePlayerCommand : AbstractCommand
{
protected override void OnExecute()
{
// 通过 Context 访问服务
var context = this.GetContext();
var container = context.Container;
var eventBus = context.EventBus;
var playerModel = container.Get<PlayerModel>();
playerModel.Health.Value = playerModel.MaxHealth.Value;
eventBus.Send(new GameStartedEvent());
var playerModel = this.GetModel<PlayerModel>();
playerModel.Health.Value -= 10;
}
}
```
### 在 Query 中使用
常用扩展包括:
- `GetModel<T>()`
- `GetSystem<T>()`
- `GetUtility<T>()`
- `GetService<T>()`
- `SendEvent(...)`
- `RegisterEvent(...)`
- `SendCommand(...)`
- `SendQuery(...)`
## 事件入口
框架事件系统仍然由上下文统一暴露:
```csharp
public class GetPlayerHealthQuery : AbstractQuery<int>
context.SendEvent(new PlayerDiedEvent());
var unRegister = context.RegisterEvent<PlayerDiedEvent>(static e =>
{
protected override int OnDo()
{
// 通过 Context 访问容器
var context = this.GetContext();
var playerModel = context.Container.Get<PlayerModel>();
return playerModel.Health.Value;
}
}
```
## GameContext 的使用
### 绑定架构到 GameContext
```csharp
public class GameArchitecture : Architecture
{
protected override void Init()
{
// 注册组件
RegisterModel(new PlayerModel());
RegisterSystem(new CombatSystem());
}
}
// 在应用启动时绑定
var architecture = new GameArchitecture();
await architecture.InitializeAsync();
// 绑定架构到 GameContext
GameContext.Bind<GameArchitecture>(architecture.Context);
```
### 从 GameContext 获取上下文
```csharp
// 在任何地方获取架构上下文
var context = GameContext.GetContext<GameArchitecture>();
// 访问服务
var playerModel = context.Container.Get<PlayerModel>();
var eventBus = context.EventBus;
```
### 使用 GameContext 的扩展方法
```csharp
// 通过扩展方法简化访问
public static class GameContextExtensions
{
public static T GetModel<T>(this IArchitectureContext context)
where T : class, IModel
{
return context.Container.Get<T>();
}
public static T GetSystem<T>(this IArchitectureContext context)
where T : class, ISystem
{
return context.Container.Get<T>();
}
}
// 使用
var context = GameContext.GetContext<GameArchitecture>();
var playerModel = context.GetModel<PlayerModel>();
var combatSystem = context.GetSystem<CombatSystem>();
```
## Context 中的服务
### EventBus - 事件总线
```csharp
var context = architecture.Context;
var eventBus = context.EventBus;
// 注册事件
eventBus.Register<PlayerDiedEvent>(e =>
{
Console.WriteLine("Player died!");
Console.WriteLine("Player died.");
});
// 发送事件
eventBus.Send(new PlayerDiedEvent());
```
### CommandBus - 命令总线
`IContextAware` 对象里也可以直接用扩展:
```csharp
var context = architecture.Context;
var commandBus = context.CommandBus;
// 发送命令
commandBus.Send(new StartGameCommand());
// 发送带返回值的命令
var damage = commandBus.Send(new CalculateDamageCommand { Input = input });
this.SendEvent(new PlayerDiedEvent());
```
### QueryBus - 查询总线
## 旧 Command / Query 兼容入口
当前上下文仍保留旧命令 / 查询体系:
- `SendCommand(ICommand)`
- `SendCommand<TResult>(ICommand<TResult>)`
- `SendCommandAsync(IAsyncCommand)`
- `SendCommandAsync<TResult>(IAsyncCommand<TResult>)`
- `SendQuery<TResult>(IQuery<TResult>)`
- `SendQueryAsync<TResult>(IAsyncQuery<TResult>)`
这部分入口主要用于兼容存量代码。新功能优先看 [cqrs](./cqrs.md)。
## 新 CQRS 入口
`IArchitectureContext` 也是当前 CQRS runtime 的主入口。最重要的方法是:
- `SendRequestAsync(...)`
- `SendRequest(...)`
- `SendAsync(...)`
- `PublishAsync(...)`
- `CreateStream(...)`
- `SendCommandAsync(...)` / `SendQueryAsync(...)` 的 CQRS 重载
示例:
```csharp
var context = architecture.Context;
var queryBus = context.QueryBus;
// 发送查询
var health = queryBus.Send(new GetPlayerHealthQuery { Input = new EmptyQueryInput() });
var playerId = await architecture.Context.SendRequestAsync(
new CreatePlayerCommand(new CreatePlayerInput("Alice")));
```
### Container - IoC 容器
如果你在 `IContextAware` 对象内部,通常直接用 `GFramework.Cqrs.Extensions` 里的扩展:
```csharp
var context = architecture.Context;
var container = context.Container;
using GFramework.Cqrs.Extensions;
// 获取已注册的组件
var playerModel = container.Get<PlayerModel>();
var combatSystem = container.Get<CombatSystem>();
// 获取所有实现某接口的组件
var allSystems = container.GetAll<ISystem>();
var playerId = await this.SendAsync(
new CreatePlayerCommand(new CreatePlayerInput("Alice")));
```
### Environment - 环境配置
## `GameContext` 现在是什么角色
```csharp
var context = architecture.Context;
var environment = context.Environment;
`GameContext` 仍然存在,但已经退到兼容和回退路径。
// 获取环境值
var gameMode = environment.Get<string>("GameMode");
var maxPlayers = environment.Get<int>("MaxPlayers");
`ContextAwareBase` 在实例未显式注入上下文时,会回退到 `GameContext.GetFirstArchitectureContext()`。这能保证部分旧代码继续工作,但它不是新代码的首选接法。
// 安全获取值
if (environment.TryGet<string>("ServerAddress", out var address))
{
Console.WriteLine($"Server: {address}");
}
```
新代码更推荐:
### Logger - 日志系统
- 让对象通过框架流程注入 `IArchitectureContext`
- 或使用 `[ContextAware]` 生成路径
- 或显式从 `architecture.Context` 启动调用链
```csharp
var context = architecture.Context;
var logger = context.Logger;
## 什么时候需要手动拿 `architecture.Context`
// 记录日志
logger.Log("Game started");
logger.LogWarning("Low memory");
logger.LogError("Failed to load resource");
```
以下场景适合直接使用 `architecture.Context`
## Context 的生命周期
- 组合根或启动代码
- 非 `IContextAware` 对象
- 测试中显式驱动请求和事件
- 你要清楚地区分“旧 Command / Query 兼容入口”和“新 CQRS 入口”
### 创建
## 继续阅读
Context 在架构初始化时自动创建:
```csharp
var architecture = new GameArchitecture();
// Context 在这里被创建
var context = architecture.Context;
```
### 使用
Context 在架构的整个生命周期中可用:
```csharp
// 初始化期间
await architecture.InitializeAsync();
// Ready 阶段
var context = architecture.Context;
var playerModel = context.Container.Get<PlayerModel>();
// 销毁前
architecture.Destroy();
```
### 销毁
Context 随着架构的销毁而销毁:
```csharp
architecture.Destroy();
// Context 不再可用
```
## 最佳实践
### 1. 通过扩展方法简化访问
```csharp
public static class ContextExtensions
{
public static T GetModel<T>(this IArchitectureContext context)
where T : class, IModel
{
return context.Container.Get<T>();
}
public static T GetSystem<T>(this IArchitectureContext context)
where T : class, ISystem
{
return context.Container.Get<T>();
}
public static void SendCommand(this IArchitectureContext context, ICommand command)
{
context.CommandBus.Send(command);
}
public static TResult SendQuery<TResult>(this IArchitectureContext context, IQuery<TResult> query)
{
return context.QueryBus.Send(query);
}
}
// 使用
var context = architecture.Context;
var playerModel = context.GetModel<PlayerModel>();
context.SendCommand(new StartGameCommand());
```
### 2. 缓存 Context 引用
```csharp
public class GameSystem : AbstractSystem
{
private IArchitectureContext _context;
protected override void OnInit()
{
// 缓存 Context 引用
_context = this.GetContext();
// 后续使用缓存的引用
_context.EventBus.Register<GameStartedEvent>(OnGameStarted);
}
private void OnGameStarted(GameStartedEvent e)
{
var playerModel = _context.Container.Get<PlayerModel>();
}
}
```
### 3. 使用 GameContext 实现全局访问
```csharp
// 在应用启动时绑定
public class GameBootstrapper
{
public async Task StartAsync()
{
var architecture = new GameArchitecture();
await architecture.InitializeAsync();
// 绑定到 GameContext
GameContext.Bind<GameArchitecture>(architecture.Context);
}
}
// 在任何地方访问
public class UIController
{
public void UpdateHealthDisplay()
{
var context = GameContext.GetContext<GameArchitecture>();
var playerModel = context.Container.Get<PlayerModel>();
// 更新 UI
healthText.text = playerModel.Health.Value.ToString();
}
}
```
### 4. 处理 Context 不可用的情况
```csharp
public class SafeGameSystem : AbstractSystem
{
protected override void OnInit()
{
try
{
var context = this.GetContext();
if (context == null)
{
Console.WriteLine("Context not available");
return;
}
var playerModel = context.Container.Get<PlayerModel>();
}
catch (Exception ex)
{
Console.WriteLine($"Error accessing context: {ex.Message}");
}
}
}
```
## Context vs Architecture
### Architecture
- **职责**:管理组件的生命周期
- **作用**:注册、初始化、销毁组件
- **访问**:通过 `GetArchitecture()` 获取
### Context
- **职责**:提供对架构服务的访问
- **作用**:访问事件总线、命令总线、查询总线等
- **访问**:通过 `GetContext()` 获取
```csharp
// Architecture 用于管理
var architecture = GameArchitecture.Interface;
architecture.RegisterModel(new PlayerModel());
// Context 用于访问服务
var context = architecture.Context;
var playerModel = context.Container.Get<PlayerModel>();
```
## 相关包
- [`architecture`](./architecture.md) - 架构核心,创建和管理 Context
- [`ioc`](./ioc.md) - IoC 容器,通过 Context 访问
- [`events`](./events.md) - 事件总线,通过 Context 访问
- [`command`](./command.md) - 命令总线,通过 Context 访问
- [`query`](./query.md) - 查询总线,通过 Context 访问
- [`environment`](./environment.md) - 环境配置,通过 Context 访问
- [`logging`](./logging.md) - 日志系统,通过 Context 访问
---
**许可证**Apache 2.0
- 架构入口:[architecture](./architecture.md)
- 生命周期:[lifecycle](./lifecycle.md)
- 旧命令系统:[command](./command.md)
- 旧查询系统:[query](./query.md)
- 新 CQRS runtime[cqrs](./cqrs.md)

View File

@ -1,656 +1,171 @@
---
title: CQRS
description: GFramework 内建 CQRS runtime用统一请求分发、通知发布和流式处理组织业务逻辑
description: 当前推荐的新请求模型,统一覆盖 command、query、notification、stream request 和 pipeline behaviors
---
# CQRS
## 概述
`GFramework.Cqrs` 是当前推荐的新请求模型 runtime。
CQRSCommand Query Responsibility Segregation命令查询职责分离是一种架构模式将数据的读取Query和修改Command操作分离。GFramework
当前内建自有 CQRS runtime通过统一的请求分发器、通知发布和流式请求管道提供类型安全、解耦的业务逻辑处理方式。
如果你在写新功能,优先使用这套模型,而不是继续扩展 `GFramework.Core.Command` / `Query` 的兼容层。
通过 CQRS你可以将复杂的业务逻辑拆分为独立的命令和查询处理器每个处理器只负责单一职责使代码更易于测试和维护。
**主要特性**
- 命令查询职责分离
- 内建请求分发与解耦设计
- 支持管道行为Behaviors
- 异步处理支持
- 与架构系统深度集成
- 支持流式处理
## 接入包
按模块安装 CQRS runtime如果希望在编译期生成 handler 注册表,再额外安装对应的 source generator
## 安装方式
```bash
dotnet add package GeWuYou.GFramework.Cqrs
dotnet add package GeWuYou.GFramework.Cqrs.Abstractions
```
# 可选:编译期生成 handler registry减少冷启动反射扫描
如果你希望消费端程序集在编译期生成 handler registry再额外安装
```bash
dotnet add package GeWuYou.GFramework.Cqrs.SourceGenerators
```
## 核心概念
## 先理解分层
### Command命令
- `GFramework.Cqrs.Abstractions`
- 纯契约层,定义请求、处理器、行为等接口
- `GFramework.Cqrs`
- 默认 runtime、dispatcher、处理器基类和上下文扩展
- `GFramework.Cqrs.SourceGenerators`
- 可选生成器,为消费端程序集生成 `ICqrsHandlerRegistry`
命令表示修改系统状态的操作,如创建、更新、删除:
## 最小示例
```csharp
using GFramework.Cqrs.Command;
using GFramework.Cqrs.Abstractions.Cqrs.Command;
消息基类和处理器基类在不同命名空间:
// 定义命令输入
public class CreatePlayerInput : ICommandInput
{
public string Name { get; set; }
public int Level { get; set; }
}
- 消息基类:`GFramework.Cqrs.Command` / `Query` / `Notification`
- 处理器基类:`GFramework.Cqrs.Cqrs.Command` / `Query` / `Notification`
// 定义命令
public class CreatePlayerCommand : CommandBase<CreatePlayerInput, int>
{
public CreatePlayerCommand(CreatePlayerInput input) : base(input) { }
}
```
### Query查询
查询表示读取系统状态的操作,不修改数据:
```csharp
using GFramework.Cqrs.Query;
using GFramework.Cqrs.Abstractions.Cqrs.Query;
// 定义查询输入
public class GetPlayerInput : IQueryInput
{
public int PlayerId { get; set; }
}
// 定义查询
public class GetPlayerQuery : QueryBase<GetPlayerInput, PlayerData>
{
public GetPlayerQuery(GetPlayerInput input) : base(input) { }
}
```
### Handler处理器
处理器负责执行命令或查询的具体逻辑:
示例:
```csharp
using GFramework.Cqrs.Command;
using GFramework.Cqrs.Cqrs.Command;
using GFramework.Cqrs.Abstractions.Cqrs.Command;
// 命令处理器
public class CreatePlayerCommandHandler : AbstractCommandHandler<CreatePlayerCommand, int>
public sealed record CreatePlayerInput(string Name) : ICommandInput;
public sealed class CreatePlayerCommand(CreatePlayerInput input)
: CommandBase<CreatePlayerInput, int>(input)
{
public override async ValueTask<int> Handle(
}
public sealed class CreatePlayerCommandHandler
: AbstractCommandHandler<CreatePlayerCommand, int>
{
public override ValueTask<int> Handle(
CreatePlayerCommand command,
CancellationToken cancellationToken)
{
var input = command.Input;
var playerModel = this.GetModel<PlayerModel>();
// 创建玩家
var playerId = playerModel.CreatePlayer(input.Name, input.Level);
return playerId;
var playerModel = Context.GetModel<PlayerModel>();
var playerId = playerModel.Create(command.Input.Name);
return ValueTask.FromResult(playerId);
}
}
```
> 说明:消息基类位于 `GFramework.Cqrs.Command` / `Query` / `Notification` 命名空间,而处理器基类位于
> `GFramework.Cqrs.Cqrs.*` 命名空间。编写最小示例时需要同时引用对应的消息与 handler 命名空间。
## 发送请求
### Dispatcher请求分发器
架构上下文会负责将命令、查询和通知路由到对应的处理器:
如果你在 `IContextAware` 对象内部:
```csharp
// 通过架构上下文发送命令
var command = new CreatePlayerCommand(new CreatePlayerInput
{
Name = "Player1",
Level = 1
});
using GFramework.Cqrs.Extensions;
var playerId = await this.SendAsync(command);
var playerId = await this.SendAsync(
new CreatePlayerCommand(new CreatePlayerInput("Alice")));
```
## 基本用法
### 定义和发送命令
如果你在组合根或测试里:
```csharp
// 1. 定义命令输入
public class SaveGameInput : ICommandInput
var playerId = await architecture.Context.SendRequestAsync(
new CreatePlayerCommand(new CreatePlayerInput("Alice")));
```
最常用的上下文入口有:
- `SendRequestAsync(...)`
- `SendAsync(...)`
- `SendQueryAsync(...)`
- `PublishAsync(...)`
- `CreateStream(...)`
## 查询、通知和流
这套 runtime 不只处理 command也统一处理
- Query
- 读路径请求
- Notification
- 一对多广播
- Stream Request
- 返回 `IAsyncEnumerable<T>`
也就是说,新代码通常不需要再分别设计“命令总线”“查询总线”和另一套通知分发语义。
## 注册处理器
在标准 `Architecture` 启动路径中CQRS runtime 会自动接入基础设施。你通常只需要在 `OnInitialize()` 里追加行为或额外程序集:
```csharp
protected override void OnInitialize()
{
public int SlotId { get; set; }
public GameData Data { get; set; }
}
RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
RegisterCqrsPipelineBehavior<PerformanceBehavior<,>>();
// 2. 定义命令
public class SaveGameCommand : CommandBase<SaveGameInput, Unit>
{
public SaveGameCommand(SaveGameInput input) : base(input) { }
}
// 3. 实现命令处理器
public class SaveGameCommandHandler : AbstractCommandHandler<SaveGameCommand>
{
public override async ValueTask<Unit> Handle(
SaveGameCommand command,
CancellationToken cancellationToken)
{
var input = command.Input;
var saveSystem = this.GetSystem<SaveSystem>();
// 保存游戏
await saveSystem.SaveAsync(input.SlotId, input.Data);
// 发送事件
this.SendEvent(new GameSavedEvent { SlotId = input.SlotId });
return Unit.Value;
}
}
// 4. 发送命令
public async Task SaveGame()
{
var command = new SaveGameCommand(new SaveGameInput
{
SlotId = 1,
Data = currentGameData
});
await this.SendAsync(command);
RegisterCqrsHandlersFromAssemblies(
[
typeof(InventoryCqrsMarker).Assembly,
typeof(BattleCqrsMarker).Assembly
]);
}
```
### 定义和发送查询
默认逻辑会:
1. 优先使用消费端程序集上的生成注册器
2. 生成注册器不可用时回退到反射扫描
3. 对同一程序集去重,避免重复注册
## Pipeline Behavior
如果你需要围绕请求处理流程插入横切逻辑,使用:
```csharp
// 1. 定义查询输入
public class GetHighScoresInput : IQueryInput
{
public int Count { get; set; } = 10;
}
// 2. 定义查询
public class GetHighScoresQuery : QueryBase<GetHighScoresInput, List<ScoreData>>
{
public GetHighScoresQuery(GetHighScoresInput input) : base(input) { }
}
// 3. 实现查询处理器
public class GetHighScoresQueryHandler : AbstractQueryHandler<GetHighScoresQuery, List<ScoreData>>
{
public override async ValueTask<List<ScoreData>> Handle(
GetHighScoresQuery query,
CancellationToken cancellationToken)
{
var input = query.Input;
var scoreModel = this.GetModel<ScoreModel>();
// 查询高分榜
var scores = await scoreModel.GetTopScoresAsync(input.Count);
return scores;
}
}
// 4. 发送查询
public async Task<List<ScoreData>> GetHighScores()
{
var query = new GetHighScoresQuery(new GetHighScoresInput
{
Count = 10
});
var scores = await this.SendQueryAsync(query);
return scores;
}
```
### 注册处理器
在架构中注册 CQRS 行为;默认会自动接入当前架构所在程序集和 `GFramework.Core` 程序集中的处理器:
```csharp
public class GameArchitecture : Architecture
{
protected override void OnInitialize()
{
// 注册通用开放泛型行为
RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
RegisterCqrsPipelineBehavior<PerformanceBehavior<,>>();
// 默认只自动扫描当前架构程序集和 GFramework.Core 程序集中的处理器
}
}
```
当前版本会优先使用源码生成的程序集级 handler registry 来注册“当前业务程序集”里的处理器;
如果该程序集没有生成注册器,或者包含生成代码无法合法引用的处理器类型,则会自动回退到运行时反射扫描。
`GFramework.Core` 等未挂接该生成器的程序集仍会继续走反射扫描。
如果处理器位于其他模块或扩展程序集中,需要额外接入对应程序集的处理器注册,而不是只依赖默认接入范围:
```csharp
public class GameArchitecture : Architecture
{
protected override void OnInitialize()
{
RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
RegisterCqrsHandlersFromAssemblies(
[
typeof(InventoryCqrsMarker).Assembly,
typeof(BattleCqrsMarker).Assembly
]);
}
}
```
`RegisterCqrsHandlersFromAssembly(...)` / `RegisterCqrsHandlersFromAssemblies(...)` 会复用与默认启动路径相同的注册逻辑:
优先使用程序集级生成注册器,失败时自动回退到反射扫描;如果同一程序集已经由默认路径或其他模块接入,框架会自动去重,避免重复注册
handler。
`RegisterCqrsPipelineBehavior<TBehavior>()` 是唯一保留的公开入口;旧的 `Mediator` 兼容别名与扩展已移除,不再继续维护。
如果你正在从旧版本迁移,只需要直接改用 `RegisterCqrsPipelineBehavior<TBehavior>()`
`RegisterMediatorBehavior<TBehavior>()` 已移除,不再保留兼容入口。
当前接口支持两种形式:
- 开放泛型行为,例如 `LoggingBehavior<,>`,用于匹配所有请求
- 封闭行为类型,例如某个只服务于单一请求的 `SpecialBehavior`
## 高级用法
### Request请求
Request 是更通用的消息类型,可以用于任何场景:
```csharp
using GFramework.Cqrs.Request;
using GFramework.Cqrs.Abstractions.Cqrs.Request;
// 定义请求输入
public class ValidatePlayerInput : IRequestInput
{
public string PlayerName { get; set; }
}
// 定义请求
public class ValidatePlayerRequest : RequestBase<ValidatePlayerInput, bool>
{
public ValidatePlayerRequest(ValidatePlayerInput input) : base(input) { }
}
// 实现请求处理器
public class ValidatePlayerRequestHandler : AbstractRequestHandler<ValidatePlayerRequest, bool>
{
public override async ValueTask<bool> Handle(
ValidatePlayerRequest request,
CancellationToken cancellationToken)
{
var input = request.Input;
var playerModel = this.GetModel<PlayerModel>();
// 验证玩家名称
var isValid = await playerModel.IsNameValidAsync(input.PlayerName);
return isValid;
}
}
```
### Notification通知
Notification 用于一对多的消息广播:
```csharp
using GFramework.Cqrs.Notification;
using GFramework.Cqrs.Abstractions.Cqrs.Notification;
// 定义通知输入
public class PlayerLevelUpInput : INotificationInput
{
public int PlayerId { get; set; }
public int NewLevel { get; set; }
}
// 定义通知
public class PlayerLevelUpNotification : NotificationBase<PlayerLevelUpInput>
{
public PlayerLevelUpNotification(PlayerLevelUpInput input) : base(input) { }
}
// 实现通知处理器 1
public class AchievementNotificationHandler : AbstractNotificationHandler<PlayerLevelUpNotification>
{
public override async ValueTask Handle(
PlayerLevelUpNotification notification,
CancellationToken cancellationToken)
{
var input = notification.Input;
// 检查成就
CheckLevelAchievements(input.PlayerId, input.NewLevel);
await Task.CompletedTask;
}
}
// 实现通知处理器 2
public class RewardNotificationHandler : AbstractNotificationHandler<PlayerLevelUpNotification>
{
public override async ValueTask Handle(
PlayerLevelUpNotification notification,
CancellationToken cancellationToken)
{
var input = notification.Input;
// 发放奖励
GiveRewards(input.PlayerId, input.NewLevel);
await Task.CompletedTask;
}
}
// 发布通知(所有处理器都会收到)
var notification = new PlayerLevelUpNotification(new PlayerLevelUpInput
{
PlayerId = 1,
NewLevel = 10
});
await this.PublishAsync(notification);
```
### Pipeline Behaviors管道行为
Behaviors 可以在处理器执行前后添加横切关注点:
```csharp
using GFramework.Core.Abstractions.Cqrs;
// 日志行为
public class LoggingBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
where TMessage : IRequest<TResponse>
{
public async ValueTask<TResponse> Handle(
TMessage message,
MessageHandlerDelegate<TMessage, TResponse> next,
CancellationToken cancellationToken)
{
var messageName = message.GetType().Name;
Console.WriteLine($"[开始] {messageName}");
var response = await next(message, cancellationToken);
Console.WriteLine($"[完成] {messageName}");
return response;
}
}
// 性能监控行为
public class PerformanceBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
where TMessage : IRequest<TResponse>
{
public async ValueTask<TResponse> Handle(
TMessage message,
MessageHandlerDelegate<TMessage, TResponse> next,
CancellationToken cancellationToken)
{
var stopwatch = Stopwatch.StartNew();
var response = await next(message, cancellationToken);
stopwatch.Stop();
var elapsed = stopwatch.ElapsedMilliseconds;
if (elapsed > 100)
{
Console.WriteLine($"警告: {message.GetType().Name} 耗时 {elapsed}ms");
}
return response;
}
}
// 注册行为
RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
RegisterCqrsPipelineBehavior<PerformanceBehavior<,>>();
```
### 验证行为
适合的场景包括:
```csharp
public class ValidationBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
where TMessage : IRequest<TResponse>
{
public async ValueTask<TResponse> Handle(
TMessage message,
MessageHandlerDelegate<TMessage, TResponse> next,
CancellationToken cancellationToken)
{
// 验证输入
if (message is IValidatable validatable)
{
var errors = validatable.Validate();
if (errors.Any())
{
throw new ValidationException(errors);
}
}
- 日志
- 性能统计
- 校验
- 审计
- 重试或统一异常封装
return await next(message, cancellationToken);
}
}
```
旧的 `Mediator` 兼容别名入口已经移除;当前公开入口只有 `RegisterCqrsPipelineBehavior<TBehavior>()`
### 流式处理
## 和旧 Command / Query 的关系
处理大量数据时使用流式处理
当前仓库同时存在两套路径:
```csharp
// 流式查询
public class GetAllPlayersStreamQuery : QueryBase<EmptyInput, IAsyncEnumerable<PlayerData>>
{
public GetAllPlayersStreamQuery() : base(new EmptyInput()) { }
}
- 旧路径
- `GFramework.Core.Command`
- `GFramework.Core.Query`
- 新路径
- `GFramework.Cqrs`
// 流式查询处理器
public class GetAllPlayersStreamQueryHandler : AbstractStreamQueryHandler<GetAllPlayersStreamQuery, PlayerData>
{
public override async IAsyncEnumerable<PlayerData> Handle(
GetAllPlayersStreamQuery query,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var playerModel = this.GetModel<PlayerModel>();
`IArchitectureContext` 仍然会兼容旧入口,但新代码应优先使用 CQRS runtime。
await foreach (var player in playerModel.GetAllPlayersAsync(cancellationToken))
{
yield return player;
}
}
}
一个简单判断规则:
// 使用流式查询
var query = new GetAllPlayersStreamQuery();
var stream = this.CreateStream(query);
- 在维护历史代码:允许继续使用旧 Command / Query
- 在写新功能或新模块:优先使用 CQRS
await foreach (var player in stream)
{
Console.WriteLine($"玩家: {player.Name}");
}
```
## 继续阅读
## 最佳实践
1. **命令和查询分离**:严格区分修改和读取操作
```csharp
✓ CreatePlayerCommand, GetPlayerQuery // 职责清晰
✗ PlayerCommand // 职责不明确
```
2. **使用有意义的命名**:命令用动词,查询用 Get
```csharp
✓ CreatePlayerCommand, UpdateScoreCommand, GetHighScoresQuery
✗ PlayerCommand, ScoreCommand, ScoresQuery
```
3. **输入验证**:在处理器中验证输入
```csharp
public override async ValueTask<int> Handle(...)
{
if (string.IsNullOrEmpty(command.Input.Name))
throw new ArgumentException("Name is required");
// 处理逻辑
}
```
4. **使用 Behaviors 处理横切关注点**:日志、性能、验证等
```csharp
RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
RegisterCqrsPipelineBehavior<ValidationBehavior<,>>();
```
5. **保持处理器简单**:一个处理器只做一件事
```csharp
✓ 处理器只负责业务逻辑,通过架构组件访问数据
✗ 处理器中包含复杂的数据访问和业务逻辑
```
6. **使用 CancellationToken**:支持操作取消
```csharp
public override async ValueTask<T> Handle(..., CancellationToken cancellationToken)
{
await someAsyncOperation(cancellationToken);
}
```
## 常见问题
### 问题Command 和 Query 有什么区别?
**解答**
- **Command**:修改系统状态,可能有副作用,通常返回 void 或简单结果
- **Query**:只读取数据,无副作用,返回查询结果
```csharp
// Command: 修改状态
CreatePlayerCommand -> 创建玩家
UpdateScoreCommand -> 更新分数
// Query: 读取数据
GetPlayerQuery -> 获取玩家信息
GetHighScoresQuery -> 获取高分榜
```
### 问题:什么时候使用 Request
**解答**
Request 是更通用的消息类型,当操作既不是纯命令也不是纯查询时使用:
```csharp
// 验证操作:读取数据并返回结果,但不修改状态
ValidatePlayerRequest
// 计算操作:基于输入计算结果
CalculateDamageRequest
```
### 问题Notification 和 Event 有什么区别?
**解答**
- **Notification**:通过内建 CQRS runtime 发送,处理器在同一请求上下文中执行
- **Event**:通过 EventBus 发送,监听器异步执行
```csharp
// Notification: 同步处理
await this.PublishAsync(notification); // 等待所有处理器完成
// Event: 异步处理
this.SendEvent(event); // 立即返回,监听器异步执行
```
### 问题:如何处理命令失败?
**解答**
使用异常或返回 Result 类型:
```csharp
// 方式 1: 抛出异常
public override async ValueTask<Unit> Handle(...)
{
if (!IsValid())
throw new InvalidOperationException("Invalid operation");
return Unit.Value;
}
// 方式 2: 返回 Result
public override async ValueTask<Result> Handle(...)
{
if (!IsValid())
return Result.Failure("Invalid operation");
return Result.Success();
}
```
### 问题:处理器可以调用其他处理器吗?
**解答**
可以,通过架构上下文继续发送新的命令或查询:
```csharp
public override async ValueTask<Unit> Handle(...)
{
// 调用其他命令
await this.SendAsync(new AnotherCommand(...));
return Unit.Value;
}
```
### 问题:如何测试处理器?
**解答**
处理器是独立的类,易于单元测试:
```csharp
[Test]
public async Task CreatePlayer_ShouldReturnPlayerId()
{
// Arrange
var handler = new CreatePlayerCommandHandler();
handler.SetContext(mockContext);
var command = new CreatePlayerCommand(new CreatePlayerInput
{
Name = "Test",
Level = 1
});
// Act
var playerId = await handler.Handle(command, CancellationToken.None);
// Assert
Assert.That(playerId, Is.GreaterThan(0));
}
```
## 相关文档
- [命令系统](/zh-CN/core/command) - 传统命令模式
- [查询系统](/zh-CN/core/query) - 传统查询模式
- [事件系统](/zh-CN/core/events) - 事件驱动架构
- [协程系统](/zh-CN/core/coroutine) - 在协程中使用 CQRS
- 架构入口:[architecture](./architecture.md)
- 上下文入口:[context](./context.md)
- 模块 README`GFramework.Cqrs/README.md`

View File

@ -1,461 +1,166 @@
---
title: 生命周期管理
description: 生命周期管理提供了标准化的组件初始化和销毁机制,确保资源的正确管理和释放
description: 当前版本的架构生命周期由阶段模型、初始化顺序、逆序销毁和生命周期钩子共同组成
---
# 生命周期管理
## 概述
`GFramework.Core` 的生命周期由 `Architecture` 统一编排,而不是让每个组件各自决定初始化时机。
生命周期管理是 GFramework 中用于管理组件初始化和销毁的核心机制。通过实现标准的生命周期接口,组件可以在适当的时机执行初始化逻辑和资源清理,确保系统的稳定性和资源的有效管理。
你真正需要关注的是:
GFramework 提供了同步和异步两套生命周期接口,适用于不同的使用场景。架构会自动管理所有注册组件的生命周期,开发者只需实现相应的接口即可。
- 阶段枚举 `ArchitecturePhase`
- 组件初始化顺序
- 逆序销毁语义
- `IArchitectureLifecycleHook`
**主要特性**
## 阶段模型
- 标准化的初始化和销毁流程
- 支持同步和异步操作
- 自动生命周期管理
- 按注册顺序初始化,按逆序销毁
- 与架构系统深度集成
当前公开阶段如下:
## 核心概念
| 阶段 | 含义 |
| --- | --- |
| `None` | 尚未开始初始化 |
| `BeforeUtilityInit` | 即将初始化工具 |
| `AfterUtilityInit` | 工具初始化完成 |
| `BeforeModelInit` | 即将初始化模型 |
| `AfterModelInit` | 模型初始化完成 |
| `BeforeSystemInit` | 即将初始化系统 |
| `AfterSystemInit` | 系统初始化完成 |
| `Ready` | 架构已完成初始化并可供稳定使用 |
| `Destroying` | 正在销毁 |
| `Destroyed` | 已销毁 |
| `FailedInitialization` | 初始化流程失败 |
### 生命周期接口层次
正常路径:
GFramework 提供了一套完整的生命周期接口:
```text
None
-> BeforeUtilityInit
-> AfterUtilityInit
-> BeforeModelInit
-> AfterModelInit
-> BeforeSystemInit
-> AfterSystemInit
-> Ready
-> Destroying
-> Destroyed
```
## 初始化顺序
注册顺序和初始化顺序不是一回事。当前框架会按组件类别统一推进:
1. `Utility`
2. `Model`
3. `System`
这保证了大多数系统在初始化时,可以安全依赖已经就绪的工具与模型。
启动方式:
```csharp
// 同步接口
public interface IInitializable
{
void Initialize();
}
var architecture = new GameArchitecture();
await architecture.InitializeAsync();
await architecture.WaitUntilReadyAsync();
```
public interface IDestroyable
{
void Destroy();
}
注册逻辑仍然写在 `OnInitialize()`
public interface ILifecycle : IInitializable, IDestroyable
{
}
// 异步接口
public interface IAsyncInitializable
{
Task InitializeAsync();
}
public interface IAsyncDestroyable
{
ValueTask DestroyAsync();
}
public interface IAsyncLifecycle : IAsyncInitializable, IAsyncDestroyable
```csharp
protected override void OnInitialize()
{
RegisterUtility(new SaveUtility());
RegisterModel(new PlayerModel());
RegisterSystem(new CombatSystem());
}
```
### 初始化阶段
## 销毁语义
组件在注册到架构后会自动进行初始化:
销毁由 `DestroyAsync()` 统一触发,框架会按逆序回收组件。
如果组件实现了异步销毁接口,框架会优先走异步路径。也就是说,新代码应优先实现:
- `IAsyncDestroyable`
- 或其他已有的异步销毁基类路径
同步 `Destroy()` 主要是兼容入口。
## 组件自己的生命周期
大多数组件不需要手写 `Initialize()`;继承框架基类即可:
```csharp
public class PlayerModel : AbstractModel
public sealed class PlayerModel : AbstractModel
{
protected override void OnInit()
{
// 初始化逻辑
Console.WriteLine("PlayerModel 初始化");
}
}
```
### 销毁阶段
当架构销毁时,所有实现了 `IDestroyable` 的组件会按注册的逆序被销毁:
```csharp
public class GameSystem : AbstractSystem
{
public void Destroy()
{
// 清理资源
Console.WriteLine("GameSystem 销毁");
}
}
```
## 基本用法
### 实现同步生命周期
最常见的方式是继承框架提供的抽象基类:
```csharp
using GFramework.Core.Model;
public class InventoryModel : AbstractModel
{
private List<Item> _items = new();
protected override void OnInit()
{
// 初始化库存
_items = new List<Item>();
Console.WriteLine("库存系统已初始化");
}
}
```
### 实现销毁逻辑
对于需要清理资源的组件,实现 `IDestroyable` 接口:
```csharp
using GFramework.Core.Abstractions.System;
using GFramework.Core.Abstractions.Lifecycle;
public class AudioSystem : ISystem, IDestroyable
{
private AudioEngine _engine;
public void Initialize()
{
_engine = new AudioEngine();
_engine.Start();
}
public void Destroy()
{
// 清理音频资源
_engine?.Stop();
_engine?.Dispose();
_engine = null;
}
}
```
### 在架构中注册
组件注册后,架构会自动管理其生命周期:
```csharp
public class GameArchitecture : Architecture
{
protected override void Init()
{
// 注册顺序Model -> System -> Utility
RegisterModel(new PlayerModel()); // 1. 初始化
RegisterModel(new InventoryModel()); // 2. 初始化
RegisterSystem(new AudioSystem()); // 3. 初始化
// 销毁顺序会自动反转:
// AudioSystem -> InventoryModel -> PlayerModel
}
}
```
## 高级用法
### 异步初始化
对于需要异步操作的组件(如加载配置、连接数据库),使用异步生命周期:
```csharp
using GFramework.Core.Abstractions.Lifecycle;
using GFramework.Core.Abstractions.System;
public class ConfigurationSystem : ISystem, IAsyncInitializable
{
private Configuration _config;
public async Task InitializeAsync()
{
// 异步加载配置文件
_config = await LoadConfigurationAsync();
Console.WriteLine("配置已加载");
}
private async Task<Configuration> LoadConfigurationAsync()
{
await Task.Delay(100); // 模拟异步操作
return new Configuration();
}
}
```
### 异步销毁
对于需要异步清理的资源(如关闭网络连接、保存数据):
```csharp
using GFramework.Core.Abstractions.Lifecycle;
public class NetworkSystem : ISystem, IAsyncDestroyable
{
private NetworkClient _client;
public void Initialize()
{
_client = new NetworkClient();
}
public async ValueTask DestroyAsync()
{
// 异步关闭连接
if (_client != null)
{
await _client.DisconnectAsync();
await _client.DisposeAsync();
}
Console.WriteLine("网络连接已关闭");
}
}
```
### 完整异步生命周期
同时实现异步初始化和销毁:
```csharp
public class DatabaseSystem : ISystem, IAsyncLifecycle
{
private DatabaseConnection _connection;
public async Task InitializeAsync()
{
// 异步连接数据库
_connection = new DatabaseConnection();
await _connection.ConnectAsync("connection-string");
Console.WriteLine("数据库已连接");
}
public async ValueTask DestroyAsync()
{
// 异步关闭数据库连接
if (_connection != null)
{
await _connection.CloseAsync();
await _connection.DisposeAsync();
}
Console.WriteLine("数据库连接已关闭");
}
}
```
### 生命周期钩子
监听架构的生命周期阶段:
```csharp
using GFramework.Core.Abstractions.Enums;
public class AnalyticsSystem : AbstractSystem
public sealed class CombatSystem : AbstractSystem
{
protected override void OnInit()
{
Console.WriteLine("分析系统初始化");
}
public override void OnArchitecturePhase(ArchitecturePhase phase)
{
switch (phase)
{
case ArchitecturePhase.Initializing:
Console.WriteLine("架构正在初始化");
break;
case ArchitecturePhase.Ready:
Console.WriteLine("架构已就绪");
StartTracking();
break;
case ArchitecturePhase.Destroying:
Console.WriteLine("架构正在销毁");
StopTracking();
break;
}
}
private void StartTracking() { }
private void StopTracking() { }
}
```
## 最佳实践
1. **优先使用抽象基类**:继承 `AbstractModel``AbstractSystem` 等基类,它们已经实现了生命周期接口
```csharp
✓ public class MyModel : AbstractModel { }
✗ public class MyModel : IModel, IInitializable { }
```
2. **初始化顺序很重要**:按依赖关系注册组件,被依赖的组件先注册
```csharp
protected override void Init()
{
RegisterModel(new ConfigModel()); // 先注册配置
RegisterModel(new PlayerModel()); // 再注册依赖配置的模型
RegisterSystem(new GameplaySystem()); // 最后注册系统
}
```
3. **销毁时释放资源**:实现 `Destroy()` 方法清理非托管资源
```csharp
public void Destroy()
{
// 释放事件订阅
_eventBus.Unsubscribe<GameEvent>(OnGameEvent);
// 释放非托管资源
_nativeHandle?.Dispose();
// 清空引用
_cache?.Clear();
}
```
4. **异步操作使用异步接口**:避免在同步方法中阻塞异步操作
```csharp
✓ public async Task InitializeAsync() { await LoadDataAsync(); }
✗ public void Initialize() { LoadDataAsync().Wait(); } // 可能死锁
```
5. **避免在初始化中访问其他组件**:初始化顺序可能导致组件尚未就绪
```csharp
✗ protected override void OnInit()
{
var system = this.GetSystem<OtherSystem>(); // 可能尚未初始化
}
✓ public override void OnArchitecturePhase(ArchitecturePhase phase)
{
if (phase == ArchitecturePhase.Ready)
{
var system = this.GetSystem<OtherSystem>(); // 安全
}
}
```
6. **使用 OnArchitecturePhase 处理跨组件依赖**:在 Ready 阶段访问其他组件
```csharp
public override void OnArchitecturePhase(ArchitecturePhase phase)
{
if (phase == ArchitecturePhase.Ready)
{
// 此时所有组件都已初始化完成
var config = this.GetModel<ConfigModel>();
ApplyConfiguration(config);
}
}
```
## 常见问题
### 问题:什么时候使用同步 vs 异步生命周期?
**解答**
- **同步**:简单的初始化逻辑,如创建对象、设置默认值
- **异步**:需要 I/O 操作的场景,如加载文件、网络请求、数据库连接
```csharp
// 同步:简单初始化
public class ScoreModel : AbstractModel
{
protected override void OnInit()
{
Score = 0; // 简单赋值
}
}
// 异步:需要 I/O
public class SaveSystem : ISystem, IAsyncInitializable
{
public async Task InitializeAsync()
{
await LoadSaveDataAsync(); // 文件 I/O
}
}
```
### 问题:组件的初始化和销毁顺序是什么?
如果你的组件需要真正的异步初始化或销毁,再补对应接口。
**解答**
## 生命周期钩子
- **初始化顺序**:按注册顺序(先注册先初始化)
- **销毁顺序**:按注册的逆序(后注册先销毁)
当你需要做横切阶段逻辑时,优先实现 `IArchitectureLifecycleHook`,而不是把这些逻辑分散到某个具体 `System` 里。
```csharp
protected override void Init()
public sealed class MetricsHook : IArchitectureLifecycleHook
{
RegisterModel(new A()); // 1. 初始化3. 销毁
RegisterModel(new B()); // 2. 初始化2. 销毁
RegisterSystem(new C()); // 3. 初始化1. 销毁
}
```
### 问题:如何在初始化时访问其他组件?
**解答**
不要在 `OnInit()` 中访问其他组件,使用 `OnArchitecturePhase()` 在 Ready 阶段访问:
```csharp
public class DependentSystem : AbstractSystem
{
protected override void OnInit()
{
// ✗ 不要在这里访问其他组件
}
public override void OnArchitecturePhase(ArchitecturePhase phase)
public void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
{
if (phase == ArchitecturePhase.Ready)
{
// ✓ 在这里安全访问其他组件
var config = this.GetModel<ConfigModel>();
Console.WriteLine("Architecture ready.");
}
}
}
```
### 问题Destroy() 方法一定会被调用吗?
**解答**
只有在正常销毁架构时才会调用。如果应用程序崩溃或被强制终止,`Destroy()` 可能不会被调用。因此:
- 不要依赖 `Destroy()` 保存关键数据
- 使用自动保存机制保护重要数据
- 非托管资源应该实现 `IDisposable` 模式
### 问题:可以在 Destroy() 中访问其他组件吗?
**解答**
不推荐。销毁时其他组件可能已经被销毁。如果必须访问,确保检查组件是否仍然可用:
注册方式:
```csharp
public void Destroy()
{
// ✗ 不安全
var system = this.GetSystem<OtherSystem>();
system.DoSomething();
// ✓ 安全
try
{
var system = this.GetSystem<OtherSystem>();
system?.DoSomething();
}
catch
{
// 组件可能已销毁
}
}
architecture.RegisterLifecycleHook(new MetricsHook());
```
## 相关文档
## 阶段监听
- [架构组件](/zh-CN/core/architecture) - 架构基础和组件注册
- [Model 层](/zh-CN/core/model) - 数据模型的生命周期
- [System 层](/zh-CN/core/system) - 业务系统的生命周期
- [异步初始化](/zh-CN/core/async-initialization) - 异步架构初始化详解
如果你只需要观察阶段变化,也可以直接订阅:
```csharp
architecture.PhaseChanged += phase =>
{
Console.WriteLine($"Phase changed: {phase}");
};
```
## 什么时候会进入 `FailedInitialization`
如果初始化流程中抛出异常,架构会切到 `FailedInitialization`。这意味着:
- `Ready` 不会被触发
- 后续诊断应先回到启动路径
- 文档示例不应假设“只要 new 了 Architecture 就一定能跑到 Ready”
## 推荐做法
- 新代码优先使用 `InitializeAsync()` / `DestroyAsync()`
- 把注册逻辑放在 `OnInitialize()`,不要沿用旧文档里的 `Init()`
- 让 `Utility` 承载底层能力,让 `Model` 承载状态,再让 `System` 消费两者
- 跨组件阶段逻辑优先写成 `IArchitectureLifecycleHook`
## 继续阅读
- 架构入口:[architecture](./architecture.md)
- 上下文入口:[context](./context.md)

View File

@ -1,532 +1,103 @@
# Query 包使用说明
# Query
## 概述
本页说明 `GFramework.Core.Query` 里的旧查询体系。
Query 包实现了 CQRS命令查询职责分离模式中的查询部分。Query 用于封装数据查询逻辑,与 Command 不同的是Query
有返回值且不应该修改系统状态。
和旧命令系统一样,它仍然保留用于兼容存量代码;新功能优先使用 [cqrs](./cqrs.md) 中的新查询模型。
查询系统是 GFramework CQRS 架构的重要组成部分,专门负责数据读取操作,与命令系统和事件系统协同工作。
## 当前仍然可用的基类
## 核心接口
旧查询体系最常见的两个基类是:
### IQuery`<TResult>`
- `AbstractQuery<TResult>`
- 无输入查询
- `AbstractQuery<TInput, TResult>`
- 带输入查询
查询接口,定义了查询的基本契约。
与旧文档不同,带输入查询现在通过构造函数接收输入,不再依赖 `Input` 属性赋值
**核心成员:**
## 无输入查询
```csharp
TResult Do(); // 执行查询并返回结果
```
using GFramework.Core.Extensions;
using GFramework.Core.Query;
## 核心类
### AbstractQuery`<TInput, TResult>`
抽象查询基类,提供了查询的基础实现。通过 `IQueryInput` 接口传递参数。
**核心方法:**
```csharp
TResult IQuery<TResult>.Do(); // 实现 IQuery 接口
protected abstract TResult OnDo(TInput input); // 抽象查询方法,接收输入参数
```
**使用方式:**
```csharp
public abstract class AbstractQuery<TInput, TResult> : ContextAwareBase, IQuery<TResult>
where TInput : IQueryInput
public sealed class GetPlayerHealthQuery : AbstractQuery<int>
{
public TResult Do() => OnDo(Input); // 执行查询
public TInput Input { get; set; } // 输入参数
protected abstract TResult OnDo(TInput input); // 子类实现查询逻辑
}
```
### AbstractAsyncQuery`<TInput, TResult>`
支持异步执行的查询基类。
**核心方法:**
```csharp
Task<TResult> IAsyncQuery<TResult>.DoAsync(); // 实现异步查询接口
protected abstract Task<TResult> OnDoAsync(TInput input); // 抽象异步查询方法
```
### EmptyQueryInput
空查询输入类,用于表示不需要任何输入参数的查询操作。
**使用方式:**
```csharp
public sealed class EmptyQueryInput : IQueryInput
{
// 作为占位符使用,适用于那些不需要额外输入参数的查询场景
}
```
### QueryBus
查询总线实现,负责执行查询并返回结果。
**核心方法:**
```csharp
TResult Send<TResult>(IQuery<TResult> query); // 发送并执行查询
```
**使用方式:**
```csharp
public sealed class QueryBus : IQueryBus
{
public TResult Send<TResult>(IQuery<TResult> query)
protected override int OnDo()
{
ArgumentNullException.ThrowIfNull(query);
return query.Do();
return this.GetModel<PlayerModel>().Health.Value;
}
}
```
## 基本使用
### 1. 定义查询
发送方式:
```csharp
// 定义查询输入
public class GetPlayerGoldInput : IQueryInput { }
var health = this.SendQuery(new GetPlayerHealthQuery());
```
// 查询玩家金币数量
public class GetPlayerGoldQuery : AbstractQuery<GetPlayerGoldInput, int>
{
protected override int OnDo(GetPlayerGoldInput input)
{
return this.GetModel<PlayerModel>().Gold.Value;
}
}
## 带输入查询
// 定义查询输入
public class GetItemCountInput : IQueryInput
{
public string ItemId { get; set; }
}
旧查询输入类型现在直接复用 CQRS 抽象层里的 `IQueryInput`
// 查询背包中指定物品的数量
public class GetItemCountQuery : AbstractQuery<GetItemCountInput, int>
```csharp
using GFramework.Core.Extensions;
using GFramework.Core.Query;
using GFramework.Cqrs.Abstractions.Cqrs.Query;
public sealed record GetItemCountInput(string ItemId) : IQueryInput;
public sealed class GetItemCountQuery(GetItemCountInput input)
: AbstractQuery<GetItemCountInput, int>(input)
{
protected override int OnDo(GetItemCountInput input)
{
var inventory = this.GetModel<InventoryModel>();
return inventory.GetItemCount(input.ItemId);
}
}
// 定义异步查询输入
public class LoadPlayerDataInput : IQueryInput
{
public string PlayerId { get; set; }
}
// 异步查询玩家数据
public class LoadPlayerDataQuery : AbstractAsyncQuery<LoadPlayerDataInput, PlayerData>
{
protected override async Task<PlayerData> OnDoAsync(LoadPlayerDataInput input)
{
var storage = this.GetUtility<IStorageUtility>();
return await storage.LoadPlayerDataAsync(input.PlayerId);
var inventoryModel = this.GetModel<InventoryModel>();
return inventoryModel.GetItemCount(input.ItemId);
}
}
```
### 2. 发送查询
```csharp
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
[ContextAware]
public partial class ShopUI : IController
{
[Export] private Button _buyButton;
[Export] private int _itemPrice = 100;
public void OnReady()
{
_buyButton.Pressed += OnBuyButtonPressed;
}
private void OnBuyButtonPressed()
{
// 查询玩家金币
var query = new GetPlayerGoldQuery { Input = new GetPlayerGoldInput() };
int playerGold = this.SendQuery(query);
if (playerGold >= _itemPrice)
{
// 发送购买命令
this.SendCommand(new BuyItemCommand { Input = new BuyItemInput { ItemId = "sword_01" } });
}
else
{
Console.WriteLine("金币不足!");
}
}
}
var count = this.SendQuery(
new GetItemCountQuery(new GetItemCountInput("potion")));
```
### 3. 在 System 中使用
## 异步查询
上下文仍然保留旧异步查询执行入口:
- `SendQueryAsync(IAsyncQuery<TResult>)`
这主要面向兼容旧 `AsyncQueryExecutor` 路径。文档不再推荐围绕旧 `QueryBus` 设计新功能。
## 发送入口
旧查询的执行入口是:
- `SendQuery<TResult>(IQuery<TResult>)`
- `SendQueryAsync<TResult>(IAsyncQuery<TResult>)`
`IContextAware` 对象内部,通常直接使用 `GFramework.Core.Extensions` 里的扩展:
```csharp
public class CombatSystem : AbstractSystem
{
protected override void OnInit()
{
// 注册事件监听
this.RegisterEvent<EnemyAttackEvent>(OnEnemyAttack);
}
private void OnEnemyAttack(EnemyAttackEvent e)
{
// 查询玩家是否已经死亡
var query = new IsPlayerDeadQuery { Input = new EmptyQueryInput() };
bool isDead = this.SendQuery(query);
if (!isDead)
{
// 执行伤害逻辑
this.SendCommand(new TakeDamageCommand { Input = new TakeDamageInput { Damage = e.Damage } });
}
}
}
public class IsPlayerDeadQuery : AbstractQuery<EmptyQueryInput, bool>
{
protected override bool OnDo(EmptyQueryInput input)
{
return this.GetModel<PlayerModel>().Health.Value <= 0;
}
}
```
using GFramework.Core.Extensions;
```
## 高级用法
## 什么时候继续保留旧查询
### 1. 带参数的复杂查询
- 你在维护现有 `Core.Query` 代码
- 当前代码已经建立在旧查询执行器之上
- 你只想修正局部行为,不想顺手迁移整条调用链
```csharp
// 定义查询输入
public class GetEnemiesInRangeInput : IQueryInput
{
public Vector3 Center { get; set; }
public float Radius { get; set; }
}
## 什么时候改用 CQRS 查询
// 查询指定范围内的敌人列表
public class GetEnemiesInRangeQuery : AbstractQuery<GetEnemiesInRangeInput, List<Enemy>>
{
protected override List<Enemy> OnDo(GetEnemiesInRangeInput input)
{
var enemySystem = this.GetSystem<EnemySpawnSystem>();
return enemySystem.GetEnemiesInRange(input.Center, input.Radius);
}
}
如果你正在写新的读取路径,优先考虑:
// 使用
var input = new GetEnemiesInRangeInput { Center = playerPosition, Radius = 10.0f };
var query = new GetEnemiesInRangeQuery { Input = input };
var enemies = this.SendQuery(query);
```
- `GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse>`
- `AbstractQueryHandler<TQuery, TResponse>`
- `architecture.Context.SendQueryAsync(...)`
### 2. 组合查询
原因很简单:新查询路径和命令、通知、流式请求共享同一 dispatcher 与行为管道。
```csharp
// 定义查询输入
public class CanUseSkillInput : IQueryInput
{
public string SkillId { get; set; }
}
// 查询玩家是否可以使用技能
public class CanUseSkillQuery : AbstractQuery<CanUseSkillInput, bool>
{
protected override bool OnDo(CanUseSkillInput input)
{
var playerModel = this.GetModel<PlayerModel>();
// 查询技能消耗
var skillCostQuery = new GetSkillCostQuery { Input = new GetSkillCostInput { SkillId = input.SkillId } };
var skillCost = this.SendQuery(skillCostQuery);
// 检查是否满足条件
return playerModel.Mana.Value >= skillCost.ManaCost
&& !this.SendQuery(new IsSkillOnCooldownQuery { Input = new IsSkillOnCooldownInput { SkillId = input.SkillId } });
}
}
public class GetSkillCostInput : IQueryInput
{
public string SkillId { get; set; }
}
public class GetSkillCostQuery : AbstractQuery<GetSkillCostInput, SkillCost>
{
protected override SkillCost OnDo(GetSkillCostInput input)
{
return this.GetModel<SkillModel>().GetSkillCost(input.SkillId);
}
}
public class IsSkillOnCooldownInput : IQueryInput
{
public string SkillId { get; set; }
}
public class IsSkillOnCooldownQuery : AbstractQuery<IsSkillOnCooldownInput, bool>
{
protected override bool OnDo(IsSkillOnCooldownInput input)
{
return this.GetModel<SkillModel>().IsOnCooldown(input.SkillId);
}
}
```
### 3. 聚合数据查询
```csharp
// 查询玩家战斗力
public class GetPlayerPowerQuery : AbstractQuery<EmptyQueryInput, int>
{
protected override int OnDo(EmptyQueryInput input)
{
var playerModel = this.GetModel<PlayerModel>();
var equipmentModel = this.GetModel<EquipmentModel>();
int basePower = playerModel.Level.Value * 10;
int equipmentPower = equipmentModel.GetTotalPower();
int buffPower = this.SendQuery(new GetBuffPowerQuery { Input = new EmptyQueryInput() });
return basePower + equipmentPower + buffPower;
}
}
// 查询玩家详细信息用于UI显示
public class GetPlayerInfoQuery : AbstractQuery<EmptyQueryInput, PlayerInfo>
{
protected override PlayerInfo OnDo(EmptyQueryInput input)
{
var playerModel = this.GetModel<PlayerModel>();
return new PlayerInfo
{
Name = playerModel.Name.Value,
Level = playerModel.Level.Value,
Health = playerModel.Health.Value,
MaxHealth = playerModel.MaxHealth.Value,
Gold = this.SendQuery(new GetPlayerGoldQuery { Input = new GetPlayerGoldInput() }),
Power = this.SendQuery(new GetPlayerPowerQuery { Input = new EmptyQueryInput() })
};
}
}
```
### 4. 跨 System 查询
```csharp
// 在 AI System 中查询玩家状态
public class EnemyAISystem : AbstractSystem
{
protected override void OnInit() { }
public void UpdateEnemyBehavior(Enemy enemy)
{
// 查询玩家位置
var playerPosQuery = new GetPlayerPositionQuery { Input = new EmptyQueryInput() };
var playerPos = this.SendQuery(playerPosQuery);
// 查询玩家是否在攻击范围内
var inRangeInput = new IsPlayerInRangeInput { Position = enemy.Position, Range = enemy.AttackRange };
bool inRange = this.SendQuery(new IsPlayerInRangeQuery { Input = inRangeInput });
if (inRange)
{
// 查询是否可以攻击
var canAttackInput = new CanEnemyAttackInput { EnemyId = enemy.Id };
bool canAttack = this.SendQuery(new CanEnemyAttackQuery { Input = canAttackInput });
if (canAttack)
{
this.SendCommand(new EnemyAttackCommand { Input = new EnemyAttackInput { EnemyId = enemy.Id } });
}
}
}
}
```
## Query 的执行机制
所有发送给查询总线的查询最终都会通过 `QueryExecutor` 来执行:
```csharp
public class QueryExecutor
{
public static TResult Execute<TResult>(IQuery<TResult> query)
{
return query.Do();
}
}
```
**特点:**
- 提供统一的查询执行机制
- 支持同步查询执行
- 与架构上下文集成
## Command vs Query
### Command命令
- **用途**:修改系统状态
- **返回值**无返回值void或有返回值
- **示例**:购买物品、造成伤害、升级角色
### Query查询
- **用途**:读取数据,不修改状态
- **返回值**:必须有返回值
- **示例**:获取金币数量、检查技能冷却、查询玩家位置
```csharp
// ❌ 错误:在 Query 中修改状态
public class BadQuery : AbstractQuery<int>
{
protected override int OnDo()
{
var model = this.GetModel<PlayerModel>();
model.Gold.Value += 100; // 不应该在 Query 中修改数据!
return model.Gold.Value;
}
}
// ✅ 正确Query 只读取数据
public class GoodQuery : AbstractQuery<int>
{
protected override int OnDo()
{
return this.GetModel<PlayerModel>().Gold.Value;
}
}
// ✅ 修改数据应该使用 Command
public class AddGoldCommand : AbstractCommand
{
private readonly int _amount;
public AddGoldCommand(int amount)
{
_amount = amount;
}
protected override void OnExecute()
{
var model = this.GetModel<PlayerModel>();
model.Gold.Value += _amount;
}
}
```
## 最佳实践
1. **查询只读取,不修改** - 保持 Query 的纯粹性
2. **小而专注** - 每个 Query 只负责一个具体的查询任务
3. **可组合** - 复杂查询可以通过组合简单查询实现
4. **避免过度查询** - 如果需要频繁查询,考虑使用 BindableProperty
5. **命名清晰** - Query 名称应该清楚表达查询意图Get、Is、Can、Has等前缀
6. **参数通过构造函数传递** - 查询需要的参数应在创建时传入
7. **查询无状态** - 查询不应该保存长期状态,执行完即可丢弃
8. **合理使用缓存** - 对于复杂计算,可以在 Model 中缓存结果
## 性能优化
### 1. 缓存查询结果
```csharp
// 在 Model 中缓存复杂计算
public class PlayerModel : AbstractModel
{
private int? _cachedPower;
public int GetPower()
{
if (_cachedPower == null)
{
_cachedPower = CalculatePower();
}
return _cachedPower.Value;
}
private int CalculatePower()
{
// 复杂计算...
return 100;
}
public void InvalidatePowerCache()
{
_cachedPower = null;
}
}
```
### 2. 批量查询
```csharp
// 一次查询多个数据,而不是多次单独查询
public class GetMultipleItemCountsQuery : AbstractQuery<Dictionary<string, int>>
{
private readonly List<string> _itemIds;
public GetMultipleItemCountsQuery(List<string> itemIds)
{
_itemIds = itemIds;
}
protected override Dictionary<string, int> OnDo()
{
var inventory = this.GetModel<InventoryModel>();
return _itemIds.ToDictionary(id => id, id => inventory.GetItemCount(id));
}
}
```
## 查询模式优势
### 1. 职责分离
- 读写操作明确分离
- 便于优化读写性能
- 降低系统复杂度
### 2. 可扩展性
- 读写可以独立扩展
- 支持不同的数据存储策略
- 便于实现读写分离
### 3. 可维护性
- 查询逻辑集中管理
- 便于重构和优化
- 降低组件间耦合
## 相关包
- [`command`](./command.md) - CQRS 的命令部分
- [`model`](./model.md) - Query 主要从 Model 获取数据
- [`system`](./system.md) - System 中可以发送 Query
- **Controller** - Controller 中可以发送 Query接口定义在 Core.Abstractions 中)
- [`extensions`](./extensions.md) - 提供 SendQuery 扩展方法
- [`architecture`](./architecture.md) - 架构核心,负责查询的分发和执行
继续阅读:[cqrs](./cqrs.md)