mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-06 16:16:44 +08:00
docs(cqrs): 收口旧版运行时别名说明
- 更新 LegacyICqrsRuntime 兼容层说明,明确旧命名空间别名与正式 CQRS runtime seam 的边界 - 补充容器基础设施回填 legacy alias 的回归测试,并收敛相关 helper 注释 - 更新 cqrs-rewrite 跟踪与 trace,记录 RP-066 的批处理结果和验证
This commit is contained in:
parent
c1dfee3c71
commit
7209fdc32d
@ -26,7 +26,7 @@
|
||||
| --- | --- |
|
||||
| `Architectures/` `Lifecycle/` `Registries/` | `IArchitecture`、上下文、模块、服务模块、阶段监听、注册表基类与生命周期契约 |
|
||||
| `Bases/` `Controller/` `Model/` `Systems/` `Utility/` `Rule/` | 组件角色接口、优先级 / key 值对象、上下文感知约束与扩展边界 |
|
||||
| `Command/` `Query/` `Cqrs/` | 旧版命令 / 查询执行器接口,以及 `ICqrsRuntime` 这类新请求模型接线契约 |
|
||||
| `Command/` `Query/` `Cqrs/` | 旧版命令 / 查询执行器接口,以及面向 CQRS runtime 的兼容别名入口 |
|
||||
| `Events/` `Property/` `State/` `StateManagement/` | 事件总线、解绑对象、可绑定属性、状态机、Store / reducer / middleware 契约 |
|
||||
| `Coroutine/` `Time/` `Pause/` `Concurrency/` | 协程状态、时间源、暂停栈、键控异步锁和统计对象 |
|
||||
| `Resource/` `Pool/` `Logging/` `Localization/` | 资源句柄、对象池、日志、日志工厂、本地化表与格式化契约 |
|
||||
@ -41,7 +41,7 @@
|
||||
| 类型族 | 代表类型 | 阅读重点 |
|
||||
| --- | --- | --- |
|
||||
| `Architectures/` `Lifecycle/` `Registries/` | `IArchitecture`、`IArchitectureContext`、`IServiceModule`、`KeyValueRegistryBase<TKey, TValue>` | 看架构、上下文、模块装配与注册表基类边界 |
|
||||
| `Command/` `Query/` `Cqrs/` | `ICommandExecutor`、`IAsyncQueryExecutor`、`ICqrsRuntime` | 看命令、查询与新请求模型的调用入口 |
|
||||
| `Command/` `Query/` `Cqrs/` | `ICommandExecutor`、`IAsyncQueryExecutor`、`ICqrsRuntime` | 看旧命令 / 查询契约,以及 CQRS runtime 的兼容别名入口 |
|
||||
| `Events/` `Property/` `State/` `StateManagement/` | `IEventBus`、`IBindableProperty<T>`、`IStateMachine`、`IStore<TState>` | 看事件分发、可绑定状态与 store 契约 |
|
||||
| `Coroutine/` `Time/` `Pause/` `Concurrency/` | `IYieldInstruction`、`ITimeProvider`、`IPauseStackManager`、`IAsyncKeyLockManager` | 看协程、时间源、暂停栈与并发协调能力 |
|
||||
| `Resource/` `Pool/` `Logging/` `Localization/` | `IResourceManager`、`IObjectPoolSystem`、`ILogger`、`ILocalizationManager` | 看资源、对象池、日志与本地化服务角色 |
|
||||
@ -63,7 +63,11 @@
|
||||
- 架构与模块入口:`IArchitecture`、`IArchitectureContext`、`IServiceModule`
|
||||
- 运行时基础设施:`IIocContainer`、`ILogger`、`IResourceManager`、`IConfigurationManager`
|
||||
- 状态与并发能力:`IStateMachine`、`IStore`、`IAsyncKeyLockManager`、`ITimeProvider`
|
||||
- 迁移与组合边界:`ICommandExecutor`、`IQueryExecutor`、`ICqrsRuntime`
|
||||
- 迁移与组合边界:`ICommandExecutor`、`IQueryExecutor`,以及旧命名空间下作为 compatibility alias 暴露的 `ICqrsRuntime`
|
||||
|
||||
`GFramework.Core.Abstractions.Cqrs.ICqrsRuntime` 当前主要用于兼容旧命名空间引用。新代码应直接依赖
|
||||
`GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime`,这样可以把请求模型、handler 和 runtime seam 保持在同一套
|
||||
CQRS 契约下。
|
||||
|
||||
## 对应文档
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Tests.Cqrs;
|
||||
using GFramework.Core.Tests.Systems;
|
||||
using GFramework.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using LegacyICqrsRuntime = GFramework.Core.Abstractions.Cqrs.ICqrsRuntime;
|
||||
|
||||
@ -171,6 +172,30 @@ public class MicrosoftDiContainerTests
|
||||
Assert.That(_container.Get<ICqrsRuntime>(), Is.SameAs(_container.Get<LegacyICqrsRuntime>()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试当容器里仅预注册正式 CQRS runtime seam 时,基础设施接线会补齐 legacy alias,
|
||||
/// 并保持新旧服务类型解析到同一实例。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RegisterInfrastructure_Should_Backfill_Legacy_Cqrs_Runtime_Alias_With_The_Same_Instance()
|
||||
{
|
||||
_container.Clear();
|
||||
|
||||
var runtime = CqrsRuntimeFactory.CreateRuntime(
|
||||
_container,
|
||||
LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher"));
|
||||
_container.Register<ICqrsRuntime>(runtime);
|
||||
|
||||
Assert.That(_container.Get<LegacyICqrsRuntime>(), Is.Null);
|
||||
|
||||
CqrsTestRuntime.RegisterInfrastructure(_container);
|
||||
|
||||
Assert.That(_container.GetAll<ICqrsRuntime>(), Has.Count.EqualTo(1));
|
||||
Assert.That(_container.GetAll<LegacyICqrsRuntime>(), Has.Count.EqualTo(1));
|
||||
Assert.That(_container.Get<ICqrsRuntime>(), Is.SameAs(runtime));
|
||||
Assert.That(_container.Get<LegacyICqrsRuntime>(), Is.SameAs(runtime));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试当没有实例时获取应返回 null 的功能
|
||||
/// </summary>
|
||||
|
||||
@ -32,6 +32,8 @@ public sealed class CqrsRuntimeModule : IServiceModule
|
||||
|
||||
/// <summary>
|
||||
/// 注册默认 CQRS runtime seam 实现。
|
||||
/// 该入口会同时补齐旧命名空间下的 <c>ICqrsRuntime</c> 兼容别名,
|
||||
/// 并保证新旧服务类型都解析到同一个 runtime 实例。
|
||||
/// </summary>
|
||||
/// <param name="container">目标依赖注入容器。</param>
|
||||
public void Register(IIocContainer container)
|
||||
@ -46,12 +48,26 @@ public sealed class CqrsRuntimeModule : IServiceModule
|
||||
var registrar = CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger);
|
||||
|
||||
container.Register(runtime);
|
||||
container.Register<LegacyICqrsRuntime>((LegacyICqrsRuntime)runtime);
|
||||
RegisterLegacyRuntimeAlias(container, runtime);
|
||||
container.Register<ICqrsHandlerRegistrar>(registrar);
|
||||
container.Register<ICqrsRegistrationService>(
|
||||
CqrsRuntimeFactory.CreateRegistrationService(registrar, registrationLogger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为旧命名空间下的 CQRS runtime 契约注册兼容别名。
|
||||
/// </summary>
|
||||
/// <param name="container">承载运行时实例的依赖注入容器。</param>
|
||||
/// <param name="runtime">当前已创建的新 CQRS runtime 实例。</param>
|
||||
/// <remarks>
|
||||
/// 旧接口仍作为兼容入口保留,因此这里明确把别名注册收敛到单独 helper,
|
||||
/// 便于后续独立评估 alias 收口,而不混入 runtime 主体行为。
|
||||
/// </remarks>
|
||||
private static void RegisterLegacyRuntimeAlias(IIocContainer container, ICqrsRuntime runtime)
|
||||
{
|
||||
container.Register<LegacyICqrsRuntime>((LegacyICqrsRuntime)runtime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化模块。
|
||||
/// </summary>
|
||||
|
||||
@ -52,6 +52,8 @@ public static class CqrsTestRuntime
|
||||
/// 这使仅使用 <see cref="MicrosoftDiContainer" /> 的测试环境也能观察与生产路径一致的 runtime 行为,
|
||||
/// 而无需完整启动服务模块管理器。
|
||||
/// 该方法按服务类型执行幂等注册,只会补齐当前容器中尚未接线的 CQRS 基础设施。
|
||||
/// 若容器里只预注册了正式 <see cref="ICqrsRuntime" /> seam,本方法也会补齐旧命名空间下的
|
||||
/// <see cref="LegacyICqrsRuntime" /> 兼容别名,并保持它与正式 seam 指向同一实例。
|
||||
/// </remarks>
|
||||
public static void RegisterInfrastructure(MicrosoftDiContainer container)
|
||||
{
|
||||
@ -63,11 +65,11 @@ public static class CqrsTestRuntime
|
||||
var notificationPublisher = container.Get<INotificationPublisher>();
|
||||
var runtime = CqrsRuntimeFactory.CreateRuntime(container, runtimeLogger, notificationPublisher);
|
||||
container.Register(runtime);
|
||||
container.Register<LegacyICqrsRuntime>((LegacyICqrsRuntime)runtime);
|
||||
RegisterLegacyRuntimeAlias(container, runtime);
|
||||
}
|
||||
else if (container.Get<LegacyICqrsRuntime>() is null)
|
||||
{
|
||||
container.Register<LegacyICqrsRuntime>((LegacyICqrsRuntime)container.GetRequired<ICqrsRuntime>());
|
||||
RegisterLegacyRuntimeAlias(container, container.GetRequired<ICqrsRuntime>());
|
||||
}
|
||||
|
||||
if (container.Get<ICqrsHandlerRegistrar>() is null)
|
||||
@ -86,6 +88,19 @@ public static class CqrsTestRuntime
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为旧命名空间下的 CQRS runtime 契约注册兼容别名。
|
||||
/// </summary>
|
||||
/// <param name="container">承载运行时实例的测试容器。</param>
|
||||
/// <param name="runtime">当前正式 CQRS runtime 实例。</param>
|
||||
/// <remarks>
|
||||
/// 测试辅助层显式保留这条 helper,避免“已存在正式 seam 时再补旧别名”的兼容语义分散在多个分支里。
|
||||
/// </remarks>
|
||||
private static void RegisterLegacyRuntimeAlias(MicrosoftDiContainer container, ICqrsRuntime runtime)
|
||||
{
|
||||
container.Register<LegacyICqrsRuntime>((LegacyICqrsRuntime)runtime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过与生产代码一致的注册入口扫描并注册指定程序集中的 CQRS 处理器。
|
||||
/// </summary>
|
||||
|
||||
@ -7,7 +7,7 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-065`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-066`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前焦点:
|
||||
- 已完成一轮 `CQRS vs Mediator` 只读评估归档,结论已沉淀到 `archive/todos/cqrs-vs-mediator-assessment-rp063.md`
|
||||
@ -34,6 +34,12 @@ CQRS 迁移与收敛。
|
||||
`Mediator` 语义收口为 `CQRS` / `ArchitectureContext`
|
||||
- 已补充 `ArchitectureContextTests` 并发 lazy-resolution 回归,锁定 `PublishAsync(...)` 与 `CreateStream(...)`
|
||||
在并发首次访问时也只会解析一次 `ICqrsRuntime`
|
||||
- 已完成一轮 `LegacyICqrsRuntime` compatibility slice 收口:
|
||||
- `CqrsRuntimeModule` 与 `GFramework.Tests.Common.CqrsTestRuntime` 现把 legacy alias 注册收敛到显式 helper
|
||||
- `MicrosoftDiContainerTests` 已补充“只预注册正式 `ICqrsRuntime` seam 时,也会回填 legacy alias 且保持同实例”的回归
|
||||
- `GFramework.Core.Abstractions/README.md`、`docs/zh-CN/abstractions/core-abstractions.md` 与
|
||||
`docs/zh-CN/core/cqrs.md` 现已明确:旧命名空间下的 `ICqrsRuntime` 仅作为 compatibility alias 保留,
|
||||
新代码应直接依赖 `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime`
|
||||
- 已将 mixed fallback 场景进一步收敛:当 runtime 允许同一程序集声明多个 `CqrsReflectionFallbackAttribute` 实例时,generator 现会把可直接引用的 fallback handlers 与仅能按名称恢复的 fallback handlers 拆分发射
|
||||
- `CqrsReflectionFallbackAttribute` 现允许多实例,以承载 `Type[]` 与字符串 fallback 元数据的组合输出
|
||||
- 已将 generator 的程序集级 fallback 元数据进一步收敛:当全部 fallback handlers 都可直接引用且 runtime 暴露 `params Type[]` 合同时,生成器现优先发射 `typeof(...)` 形式的 fallback 元数据
|
||||
@ -216,6 +222,12 @@ CQRS 迁移与收敛。
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests"`
|
||||
- 结果:通过
|
||||
- 备注:`41/41` 通过;确认 CQRS 基础设施默认接线与容器行为未回归
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests"`
|
||||
- 结果:通过
|
||||
- 备注:`42/42` 通过;本轮新增 legacy alias 回填回归后,确认正式 seam 与旧命名空间 alias 仍指向同一实例
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
|
||||
- 结果:通过
|
||||
- 备注:`0 warning / 0 error`;确认 legacy alias helper 收敛与文档更新未引入 `GFramework.Core` 模块构建告警
|
||||
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
- 结果:通过
|
||||
- 备注:`0 warning / 0 error`;确认三份 `Mediator` 命名收口后的 CQRS 测试项目构建仍然干净
|
||||
@ -226,5 +238,5 @@ CQRS 迁移与收敛。
|
||||
## 下一步
|
||||
|
||||
1. 基于已落地的 notification publisher seam,评估是否需要第二阶段公开配置面、并行 publisher 或 telemetry decorator
|
||||
2. 继续以 `dispatch/invoker` 生成前移为优先对象,补一轮面向实现的设计评估
|
||||
3. 单独规划旧 `Command` / `Query` API 与 `LegacyICqrsRuntime` 的收口顺序;`Mediator` 测试命名收口已完成,可移出该子问题
|
||||
2. 继续以 `dispatch/invoker` 生成前移为优先对象,优先尝试 “generated request invoker provider + dispatcher fallback” 这条最小实现切片
|
||||
3. 单独规划旧 `Command` / `Query` API 的收口顺序;`LegacyICqrsRuntime` compatibility slice 已收口到显式 helper 与专门测试,可暂时移出最高优先级
|
||||
|
||||
@ -2,6 +2,37 @@
|
||||
|
||||
## 2026-04-30
|
||||
|
||||
### 阶段:LegacyICqrsRuntime compatibility slice 收口(CQRS-REWRITE-RP-066)
|
||||
|
||||
- 继续按 `gframework-batch-boot 50` 执行,基线仍为本地现有 `origin/main`
|
||||
- 在 `RP-065` 之后复算 branch diff,相对 `origin/main` 仍为 `19 files`,明显低于 `50 files` stop condition,因此继续下一批
|
||||
- 本轮按“关键路径本地、非冲突文档委派”的方式拆成两个切片:
|
||||
- worker:`GFramework.Core.Abstractions/README.md`、`docs/zh-CN/abstractions/core-abstractions.md`、`docs/zh-CN/core/cqrs.md`
|
||||
- 主线程:`GFramework.Core/Services/Modules/CqrsRuntimeModule.cs`、`GFramework.Tests.Common/CqrsTestRuntime.cs`、`GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs`
|
||||
- 接受只读 subagent 结论后,将 `LegacyICqrsRuntime` 定位为“容器兼容层”,明确本轮不删除别名、不改 dispatcher 主体、不与旧 `Command` / `Query` API 清理混做
|
||||
- 主线程已完成:
|
||||
- `CqrsRuntimeModule` 把 legacy alias 注册收敛到 `RegisterLegacyRuntimeAlias(...)` helper,并在 XML 文档里明确新旧服务类型解析到同一 runtime 实例
|
||||
- `CqrsTestRuntime.RegisterInfrastructure(...)` 现也通过同名 helper 补齐 legacy alias;当容器只预注册正式 `ICqrsRuntime` seam 时,会在幂等接线时回填旧命名空间 alias
|
||||
- `MicrosoftDiContainerTests` 新增 `RegisterInfrastructure_Should_Backfill_Legacy_Cqrs_Runtime_Alias_With_The_Same_Instance`,锁定“只存在正式 seam 时也会补旧 alias,且两者仍指向同一实例”的兼容合同
|
||||
- worker 已完成文档收口:
|
||||
- `GFramework.Core.Abstractions/README.md`
|
||||
- `docs/zh-CN/abstractions/core-abstractions.md`
|
||||
- `docs/zh-CN/core/cqrs.md`
|
||||
- 三处文档都已明确:`GFramework.Core.Abstractions.Cqrs.ICqrsRuntime` 只是旧命名空间下保留的 compatibility alias,新代码应依赖 `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime`
|
||||
|
||||
### 验证
|
||||
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests"`
|
||||
- 结果:通过,`42/42` passed
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
|
||||
### 当前下一步
|
||||
|
||||
1. 在保持 branch diff 低于阈值的前提下,回到 `dispatch/invoker` 生成前移主线
|
||||
2. 优先尝试只覆盖 request 路径的 generated invoker/provider 最小切片,避免一次卷入 notification / stream / pipeline executor
|
||||
3. 下一次 batch 结束后继续复算 branch diff,确认距 `50 files` stop condition 的剩余 headroom
|
||||
|
||||
### 阶段:测试命名收口与 ArchitectureContext lazy-resolution 回归(CQRS-REWRITE-RP-065)
|
||||
|
||||
- 继续按 `gframework-batch-boot 50` 执行,基线仍为本地现有 `origin/main`
|
||||
|
||||
@ -75,7 +75,10 @@ public sealed class DiagnosticsFeature
|
||||
- 架构与模块入口:`IArchitecture`、`IArchitectureContext`、`IServiceModule`
|
||||
- 运行时基础设施:`IIocContainer`、`ILogger`、`IResourceManager`、`IConfigurationManager`
|
||||
- 状态与并发能力:`IStateMachine`、`IStore`、`IAsyncKeyLockManager`、`ITimeProvider`
|
||||
- 迁移与组合边界:`ICommandExecutor`、`IQueryExecutor`、`ICqrsRuntime`
|
||||
- 迁移与组合边界:`ICommandExecutor`、`IQueryExecutor`,以及旧命名空间下作为 compatibility alias 暴露的 `ICqrsRuntime`
|
||||
|
||||
`GFramework.Core.Abstractions.Cqrs.ICqrsRuntime` 当前主要承担旧命名空间兼容入口的角色。编写新模块或新增请求处理逻辑时,
|
||||
应直接引用 `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime`,让 runtime seam 与 CQRS 请求契约保持一致。
|
||||
|
||||
## 契约族阅读入口
|
||||
|
||||
@ -85,7 +88,7 @@ public sealed class DiagnosticsFeature
|
||||
| --- | --- | --- |
|
||||
| `Architectures/` | `IArchitecture`、`IArchitectureContext`、`IArchitectureServices`、`IServiceModule` | 架构上下文、服务访问面与模块安装 / 生命周期约束 |
|
||||
| `Lifecycle/` `Registries/` | `ILifecycle`、`IAsyncInitializable`、`IRegistry<T, TR>`、`KeyValueRegistryBase<TKey, TValue>` | 初始化 / 销毁阶段和注册表抽象边界 |
|
||||
| `Command/` `Query/` `Cqrs/` | `ICommandExecutor`、`IAsyncCommand<TResult>`、`IQueryExecutor`、`ICqrsRuntime` | 旧命令 / 查询接口与新请求模型之间的兼容和迁移边界 |
|
||||
| `Command/` `Query/` `Cqrs/` | `ICommandExecutor`、`IAsyncCommand<TResult>`、`IQueryExecutor`、`ICqrsRuntime` | 旧命令 / 查询接口,以及 CQRS runtime compatibility alias 的迁移边界 |
|
||||
| `Events/` `Property/` | `IEventBus`、`IEventFilter<T>`、`IBindableProperty<T>`、`IReadonlyBindableProperty<T>` | 事件传播、过滤、解绑对象和属性订阅语义 |
|
||||
| `State/` `StateManagement/` | `IStateMachine`、`IAsyncState`、`IStore<TState>`、`IStoreMiddleware<TState>` | 状态机契约与 Store 的 reducer / middleware / diagnostics 边界 |
|
||||
| `Coroutine/` `Time/` `Pause/` `Concurrency/` | `IYieldInstruction`、`ICoroutineStatistics`、`ITimeProvider`、`IPauseStackManager`、`IAsyncKeyLockManager` | 调度模型、时间源、暂停栈和异步锁契约 |
|
||||
|
||||
@ -13,6 +13,9 @@ description: Cqrs 模块族的运行时、契约层、生成器入口,以及
|
||||
|
||||
如果你在写新功能,优先使用这套请求模型,而不是继续扩展 `GFramework.Core.Command` / `Query` 的兼容层。
|
||||
|
||||
如果你在查找 `ICqrsRuntime`,请把 `GFramework.Core.Abstractions.Cqrs.ICqrsRuntime` 理解为旧命名空间下保留的
|
||||
legacy compatibility alias。新代码应直接依赖 `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime`。
|
||||
|
||||
## 模块族边界
|
||||
|
||||
| 模块 | 角色 | 何时安装 |
|
||||
@ -202,6 +205,12 @@ RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
|
||||
|
||||
`IArchitectureContext` 仍然兼容旧入口,但新代码应优先使用 CQRS runtime。
|
||||
|
||||
这里有两个边界需要分开理解:
|
||||
|
||||
- 旧 `Command` / `Query` 入口仍可用于维护历史调用链
|
||||
- 旧命名空间下的 `ICqrsRuntime` 只是为了兼容既有引用而保留的 alias;面向新代码时,应直接使用
|
||||
`GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime`
|
||||
|
||||
一个简单判断规则:
|
||||
|
||||
- 在维护历史代码:允许继续使用旧 Command / Query
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user