Merge pull request #220 from GeWuYou/refactor/cqrs-architecture-decoupling-todo-1

Replace Mediator runtime with built-in CQRS
This commit is contained in:
gewuyou 2026-04-14 22:12:40 +08:00 committed by GitHub
commit 156fd4df2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 1253 additions and 309 deletions

View File

@ -16,6 +16,8 @@ reviews:
auto_review:
enabled: true
drafts: false # draft PR 不 review
base_branches:
- refactor/cqrs-architecture-decoupling
chat:
auto_reply: true

View File

@ -244,6 +244,16 @@ bash scripts/validate-csharp-naming.sh
- Tracking updates MUST reflect completed work, newly discovered issues, validation results, and the next recommended
recovery point.
- Completing code changes without updating the active tracking document is considered incomplete work.
- For any multi-step refactor, migration, or cross-module task, contributors MUST create or adopt a dedicated recovery
document under `local-plan/todos/` before making substantive code changes.
- Recovery documents MUST record the current phase, the active recovery point identifier, known risks, and the next
recommended resume step so another contributor or subagent can continue the work safely.
- Contributors MUST maintain a matching execution trace under `local-plan/traces/` for complex work. The trace should
record the current date, key decisions, validation milestones, and the immediate next step.
- When a task spans multiple commits or is likely to exceed a single agent context window, update both the recovery
document and the trace at each meaningful milestone before pausing or handing work off.
- If subagents are used on a complex task, the main agent MUST capture the delegated scope and any accepted findings in
the active recovery document or trace before continuing implementation.
### Repository Documentation

View File

@ -1,18 +1,23 @@
using GFramework.Core.Abstractions.Command;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Environment;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Query;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
using Mediator;
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
namespace GFramework.Core.Abstractions.Architectures;
/// <summary>
/// 架构上下文接口,提供对系统、模型、工具类的访问以及命令、查询、事件的发送和注册功能
/// 架构上下文接口,统一暴露框架组件访问、兼容旧命令/查询总线,以及当前推荐的 CQRS 运行时入口。
/// </summary>
/// <remarks>
/// <para>旧的 <c>GFramework.Core.Abstractions.Command</c> 与 <c>GFramework.Core.Abstractions.Query</c> 契约会继续通过原有 Command/Query Executor 路径执行,以保证存量代码兼容。</para>
/// <para>新的 <c>GFramework.Core.Abstractions.Cqrs</c> 契约由内置 CQRS dispatcher 统一处理,支持 request pipeline、notification publish 与 stream request。</para>
/// <para>新功能优先使用 <see cref="SendRequestAsync{TResponse}(IRequest{TResponse},CancellationToken)" />、<see cref="SendAsync{TCommand}(TCommand,CancellationToken)" /> 与对应的 CQRS Command/Query 重载;迁移旧代码时可先保留旧入口,再逐步替换为 CQRS 请求模型。</para>
/// </remarks>
public interface IArchitectureContext
{
/// <summary>
@ -104,87 +109,91 @@ public interface IArchitectureContext
IReadOnlyList<TUtility> GetUtilitiesByPriority<TUtility>() where TUtility : class, IUtility;
/// <summary>
/// 发送一个命令
/// 发送一个旧版命令
/// </summary>
/// <param name="command">要发送的命令</param>
/// <param name="command">要发送的旧版命令</param>
void SendCommand(ICommand command);
/// <summary>
/// 发送一个带返回值命令
/// 发送一个旧版带返回值命令
/// </summary>
/// <typeparam name="TResult">命令执行结果类型</typeparam>
/// <param name="command">要发送的命令</param>
/// <returns>命令执行结果</returns>
TResult SendCommand<TResult>(Command.ICommand<TResult> command);
/// <typeparam name="TResult">命令执行结果类型</typeparam>
/// <param name="command">要发送的旧版命令</param>
/// <returns>命令执行结果</returns>
TResult SendCommand<TResult>(ICommand<TResult> command);
/// <summary>
/// [Mediator] 发送命令的同步版本(不推荐,仅用于兼容性)
/// 发送一个新版 CQRS 命令并返回结果。
/// </summary>
/// <typeparam name="TResponse">命令响应类型</typeparam>
/// <param name="command">要发送的命令对象</param>
/// <returns>命令执行结果</returns>
TResponse SendCommand<TResponse>(Mediator.ICommand<TResponse> command);
/// <typeparam name="TResponse">命令响应类型。</typeparam>
/// <param name="command">要发送的 CQRS 命令。</param>
/// <returns>命令执行结果。</returns>
/// <remarks>
/// 这是迁移后的推荐命令入口。无返回值命令应实现 <c>IRequest&lt;Unit&gt;</c>,并优先通过 <see cref="SendAsync{TCommand}(TCommand,CancellationToken)" /> 调用。
/// </remarks>
TResponse SendCommand<TResponse>(Cqrs.Command.ICommand<TResponse> command);
/// <summary>
/// 发送并异步执行一个命令
/// 异步发送一个旧版命令。
/// </summary>
/// <param name="command">要发送的命令</param>
/// <param name="command">要发送的旧版命令</param>
Task SendCommandAsync(IAsyncCommand command);
/// <summary>
/// [Mediator] 异步发送命令并返回结果
/// 通过Mediator模式发送命令请求支持取消操作
/// 异步发送一个新版 CQRS 命令并返回结果。
/// </summary>
/// <typeparam name="TResponse">命令响应类型</typeparam>
/// <param name="command">要发送的命令对象</param>
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>包含命令执行结果的ValueTask</returns>
ValueTask<TResponse> SendCommandAsync<TResponse>(Mediator.ICommand<TResponse> command,
/// <typeparam name="TResponse">命令响应类型</typeparam>
/// <param name="command">要发送的 CQRS 命令。</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>包含命令执行结果的值任务。</returns>
ValueTask<TResponse> SendCommandAsync<TResponse>(Cqrs.Command.ICommand<TResponse> command,
CancellationToken cancellationToken = default);
/// <summary>
/// 发送并异步执行一个带返回值的命令
/// 异步发送一个旧版带返回值命令。
/// </summary>
/// <typeparam name="TResult">命令执行结果类型</typeparam>
/// <param name="command">要发送的命令</param>
/// <returns>命令执行结果</returns>
/// <typeparam name="TResult">命令执行结果类型</typeparam>
/// <param name="command">要发送的旧版命令</param>
/// <returns>命令执行结果</returns>
Task<TResult> SendCommandAsync<TResult>(IAsyncCommand<TResult> command);
/// <summary>
/// 发送一个查询请求
/// 发送一个旧版查询请求
/// </summary>
/// <typeparam name="TResult">查询结果类型</typeparam>
/// <param name="query">要发送的查询</param>
/// <returns>查询结果</returns>
TResult SendQuery<TResult>(Query.IQuery<TResult> query);
/// <typeparam name="TResult">查询结果类型</typeparam>
/// <param name="query">要发送的旧版查询</param>
/// <returns>查询结果</returns>
TResult SendQuery<TResult>(IQuery<TResult> query);
/// <summary>
/// [Mediator] 发送查询的同步版本(不推荐,仅用于兼容性)
/// 发送一个新版 CQRS 查询并返回结果。
/// </summary>
/// <typeparam name="TResponse">查询响应类型</typeparam>
/// <param name="query">要发送的查询对象</param>
/// <returns>查询结果</returns>
TResponse SendQuery<TResponse>(Mediator.IQuery<TResponse> query);
/// <typeparam name="TResponse">查询响应类型。</typeparam>
/// <param name="query">要发送的 CQRS 查询。</param>
/// <returns>查询结果。</returns>
/// <remarks>
/// 这是迁移后的推荐查询入口。新查询应优先实现 <c>GFramework.Core.Abstractions.Cqrs.Query.IQuery&lt;TResponse&gt;</c>。
/// </remarks>
TResponse SendQuery<TResponse>(Cqrs.Query.IQuery<TResponse> query);
/// <summary>
/// 异步发送一个查询请求
/// 异步发送一个旧版查询请求
/// </summary>
/// <typeparam name="TResult">查询结果类型</typeparam>
/// <param name="query">要发送的异步查询</param>
/// <returns>查询结果</returns>
/// <typeparam name="TResult">查询结果类型</typeparam>
/// <param name="query">要发送的旧版异步查询</param>
/// <returns>查询结果</returns>
Task<TResult> SendQueryAsync<TResult>(IAsyncQuery<TResult> query);
/// <summary>
/// [Mediator] 异步发送查询并返回结果
/// 通过Mediator模式发送查询请求支持取消操作
/// 异步发送一个新版 CQRS 查询并返回结果。
/// </summary>
/// <typeparam name="TResponse">查询响应类型</typeparam>
/// <param name="query">要发送的查询对象</param>
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>包含查询结果的ValueTask</returns>
ValueTask<TResponse> SendQueryAsync<TResponse>(Mediator.IQuery<TResponse> query,
/// <typeparam name="TResponse">查询响应类型</typeparam>
/// <param name="query">要发送的 CQRS 查询。</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>包含查询结果的值任务。</returns>
ValueTask<TResponse> SendQueryAsync<TResponse>(Cqrs.Query.IQuery<TResponse> query,
CancellationToken cancellationToken = default);
/// <summary>
@ -216,28 +225,40 @@ public interface IArchitectureContext
void UnRegisterEvent<TEvent>(Action<TEvent> onEvent);
/// <summary>
/// 发送请求(统一处理 Command/Query
/// 发送新版 CQRS 请求,并统一处理命令与查询。
/// </summary>
/// <remarks>
/// 这是自有 CQRS 运行时的主入口。新代码应优先通过该方法或 <see cref="SendAsync{TCommand}(TCommand,CancellationToken)" /> 进入 dispatcher。
/// </remarks>
ValueTask<TResponse> SendRequestAsync<TResponse>(
IRequest<TResponse> request,
CancellationToken cancellationToken = default);
/// <summary>
/// 发送请求(同步版本,不推荐)
/// 发送新版 CQRS 请求的同步包装版本。
/// </summary>
/// <remarks>
/// 仅为兼容同步调用链保留;新代码应优先使用异步入口,避免阻塞当前线程。
/// </remarks>
TResponse SendRequest<TResponse>(IRequest<TResponse> request);
/// <summary>
/// 发布通知(一对多事件)
/// 发布新版 CQRS 通知。
/// </summary>
/// <remarks>
/// 该入口用于一对多通知分发,与框架级 <c>EventBus</c> 事件系统并存,适合围绕请求处理过程传播领域通知。
/// </remarks>
ValueTask PublishAsync<TNotification>(
TNotification notification,
CancellationToken cancellationToken = default)
where TNotification : INotification;
/// <summary>
/// 创建流式请求(用于大数据集)
/// 创建新版 CQRS 流式请求。
/// </summary>
/// <remarks>
/// 适用于需要按序惰性产出大量结果的场景。调用方应消费返回的异步序列,而不是回退到旧版查询总线。
/// </remarks>
IAsyncEnumerable<TResponse> CreateStream<TResponse>(
IStreamRequest<TResponse> request,
CancellationToken cancellationToken = default);
@ -245,7 +266,7 @@ public interface IArchitectureContext
// === 便捷扩展方法 ===
/// <summary>
/// 发送命令(无返回值)
/// 发送一个无返回值的新版 CQRS 命令。
/// </summary>
ValueTask SendAsync<TCommand>(
TCommand command,
@ -253,7 +274,7 @@ public interface IArchitectureContext
where TCommand : IRequest<Unit>;
/// <summary>
/// 发送命令(有返回值)
/// 发送一个有返回值的新版 CQRS 请求。
/// </summary>
ValueTask<TResponse> SendAsync<TResponse>(
IRequest<TResponse> command,
@ -265,4 +286,4 @@ public interface IArchitectureContext
/// </summary>
/// <returns>环境对象实例</returns>
IEnvironment GetEnvironment();
}
}

View File

@ -0,0 +1,25 @@
namespace GFramework.Core.Abstractions.Cqrs.Command;
/// <summary>
/// 表示一个 CQRS 命令。
/// 命令通常用于修改系统状态。
/// </summary>
/// <typeparam name="TResponse">命令响应类型。</typeparam>
public interface ICommand<out TResponse> : IRequest<TResponse>
{
}
/// <summary>
/// 表示一个无显式返回值的 CQRS 命令。
/// </summary>
public interface ICommand : ICommand<Unit>
{
}
/// <summary>
/// 表示一个流式 CQRS 命令。
/// </summary>
/// <typeparam name="TResponse">流式响应元素类型。</typeparam>
public interface IStreamCommand<out TResponse> : IStreamRequest<TResponse>
{
}

View File

@ -0,0 +1,9 @@
namespace GFramework.Core.Abstractions.Cqrs;
/// <summary>
/// 表示一个一对多发布的通知消息。
/// 通知不要求返回值,允许被零个或多个处理器消费。
/// </summary>
public interface INotification
{
}

View File

@ -0,0 +1,17 @@
namespace GFramework.Core.Abstractions.Cqrs;
/// <summary>
/// 表示处理通知消息的处理器契约。
/// </summary>
/// <typeparam name="TNotification">通知类型。</typeparam>
public interface INotificationHandler<in TNotification>
where TNotification : INotification
{
/// <summary>
/// 处理通知消息。
/// </summary>
/// <param name="notification">要处理的通知。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>异步处理任务。</returns>
ValueTask Handle(TNotification notification, CancellationToken cancellationToken);
}

View File

@ -0,0 +1,22 @@
namespace GFramework.Core.Abstractions.Cqrs;
/// <summary>
/// 定义 CQRS 请求处理前后的管道行为。
/// </summary>
/// <typeparam name="TRequest">请求类型。</typeparam>
/// <typeparam name="TResponse">响应类型。</typeparam>
public interface IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
/// <summary>
/// 处理当前请求,并决定是否继续调用后续行为或最终处理器。
/// </summary>
/// <param name="message">当前请求消息。</param>
/// <param name="next">下一个处理委托。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>请求响应。</returns>
ValueTask<TResponse> Handle(
TRequest message,
MessageHandlerDelegate<TRequest, TResponse> next,
CancellationToken cancellationToken);
}

View File

@ -0,0 +1,10 @@
namespace GFramework.Core.Abstractions.Cqrs;
/// <summary>
/// 表示一个有响应的 CQRS 请求。
/// 该接口是命令、查询以及其他请求语义的统一基接口。
/// </summary>
/// <typeparam name="TResponse">请求响应类型。</typeparam>
public interface IRequest<out TResponse>
{
}

View File

@ -0,0 +1,18 @@
namespace GFramework.Core.Abstractions.Cqrs;
/// <summary>
/// 表示处理单个 CQRS 请求的处理器契约。
/// </summary>
/// <typeparam name="TRequest">请求类型。</typeparam>
/// <typeparam name="TResponse">响应类型。</typeparam>
public interface IRequestHandler<in TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
/// <summary>
/// 处理指定请求并返回结果。
/// </summary>
/// <param name="request">要处理的请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>请求结果。</returns>
ValueTask<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}

View File

@ -0,0 +1,10 @@
namespace GFramework.Core.Abstractions.Cqrs;
/// <summary>
/// 表示一个流式 CQRS 请求。
/// 请求处理器可以逐步产生响应序列,而不是一次性返回完整结果。
/// </summary>
/// <typeparam name="TResponse">流式响应元素类型。</typeparam>
public interface IStreamRequest<out TResponse>
{
}

View File

@ -0,0 +1,18 @@
namespace GFramework.Core.Abstractions.Cqrs;
/// <summary>
/// 表示处理流式 CQRS 请求的处理器契约。
/// </summary>
/// <typeparam name="TRequest">流式请求类型。</typeparam>
/// <typeparam name="TResponse">流式响应元素类型。</typeparam>
public interface IStreamRequestHandler<in TRequest, out TResponse>
where TRequest : IStreamRequest<TResponse>
{
/// <summary>
/// 处理流式请求并返回异步响应序列。
/// </summary>
/// <param name="request">要处理的请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>异步响应序列。</returns>
IAsyncEnumerable<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}

View File

@ -0,0 +1,19 @@
namespace GFramework.Core.Abstractions.Cqrs;
/// <summary>
/// 表示 CQRS 请求在管道中继续向下执行的处理委托。
/// </summary>
/// <remarks>
/// <para>管道行为可以通过不调用该委托来短路请求处理。</para>
/// <para>除显式实现重试等高级语义外,行为通常应最多调用一次该委托,以维持单次请求分发的确定性。</para>
/// <para>调用方应传递当前收到的 <paramref name="cancellationToken" />,确保取消信号沿整条管道一致传播。</para>
/// </remarks>
/// <typeparam name="TRequest">请求类型。</typeparam>
/// <typeparam name="TResponse">响应类型。</typeparam>
/// <param name="message">当前请求消息。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>请求响应。</returns>
public delegate ValueTask<TResponse> MessageHandlerDelegate<in TRequest, TResponse>(
TRequest message,
CancellationToken cancellationToken)
where TRequest : IRequest<TResponse>;

View File

@ -0,0 +1,18 @@
namespace GFramework.Core.Abstractions.Cqrs.Query;
/// <summary>
/// 表示一个 CQRS 查询。
/// 查询用于读取数据,不应产生副作用。
/// </summary>
/// <typeparam name="TResponse">查询响应类型。</typeparam>
public interface IQuery<out TResponse> : IRequest<TResponse>
{
}
/// <summary>
/// 表示一个流式 CQRS 查询。
/// </summary>
/// <typeparam name="TResponse">流式响应元素类型。</typeparam>
public interface IStreamQuery<out TResponse> : IStreamRequest<TResponse>
{
}

View File

@ -0,0 +1,13 @@
namespace GFramework.Core.Abstractions.Cqrs;
/// <summary>
/// 表示没有实际返回值的 CQRS 响应类型。
/// 该类型用于统一命令与请求的泛型签名,避免引入外部库的 <c>Unit</c> 定义。
/// </summary>
public readonly record struct Unit
{
/// <summary>
/// 获取默认的空响应实例。
/// </summary>
public static Unit Value { get; } = new();
}

View File

@ -2,14 +2,13 @@ using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Architectures;
using GFramework.Core.Logging;
using Mediator;
using Microsoft.Extensions.DependencyInjection;
using GfCqrs = GFramework.Core.Abstractions.Cqrs;
namespace GFramework.Core.Tests.Architectures;
/// <summary>
/// 验证 Architecture 通过 <c>ArchitectureModules</c> 暴露出的模块安装与 Mediator 行为注册能力。
/// 这些测试覆盖模块安装回调和中介管道行为接入,确保模块管理器仍然保持可观察行为不变。
/// 验证 Architecture 通过 <c>ArchitectureModules</c> 暴露出的模块安装与 CQRS 行为注册能力。
/// 这些测试覆盖模块安装回调和请求管道行为接入,确保模块管理器仍然保持可观察行为不变。
/// </summary>
[TestFixture]
public class ArchitectureModulesBehaviorTests
@ -57,7 +56,7 @@ public class ArchitectureModulesBehaviorTests
}
/// <summary>
/// 验证注册的 Mediator 行为会参与请求管道执行。
/// 验证注册的 CQRS 行为会参与请求管道执行。
/// </summary>
[Test]
public async Task RegisterMediatorBehavior_Should_Apply_Pipeline_Behavior_To_Request()
@ -83,12 +82,6 @@ public class ArchitectureModulesBehaviorTests
/// </summary>
private sealed class ModuleTestArchitecture(Action<ModuleTestArchitecture> registrationAction) : Architecture
{
/// <summary>
/// 打开 Mediator 服务注册,以便测试中介行为接入。
/// </summary>
public override Action<IServiceCollection>? Configurator =>
services => services.AddMediator(options => { options.ServiceLifetime = ServiceLifetime.Singleton; });
/// <summary>
/// 在初始化阶段执行测试注入的模块注册逻辑。
/// </summary>
@ -136,14 +129,14 @@ public class ArchitectureModulesBehaviorTests
/// <summary>
/// 用于验证管道行为注册是否生效的测试请求。
/// </summary>
public sealed class ModuleBehaviorRequest : IRequest<string>
public sealed class ModuleBehaviorRequest : GfCqrs.IRequest<string>
{
}
/// <summary>
/// 处理测试请求的处理器。
/// </summary>
public sealed class ModuleBehaviorRequestHandler : IRequestHandler<ModuleBehaviorRequest, string>
public sealed class ModuleBehaviorRequestHandler : GfCqrs.IRequestHandler<ModuleBehaviorRequest, string>
{
/// <summary>
/// 返回固定结果,便于聚焦验证管道行为是否执行。
@ -162,8 +155,8 @@ public sealed class ModuleBehaviorRequestHandler : IRequestHandler<ModuleBehavio
/// </summary>
/// <typeparam name="TRequest">请求类型。</typeparam>
/// <typeparam name="TResponse">响应类型。</typeparam>
public sealed class TrackingPipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
public sealed class TrackingPipelineBehavior<TRequest, TResponse> : GfCqrs.IPipelineBehavior<TRequest, TResponse>
where TRequest : GfCqrs.IRequest<TResponse>
{
/// <summary>
/// 获取当前测试进程中该请求类型对应的行为触发次数。
@ -178,11 +171,10 @@ public sealed class TrackingPipelineBehavior<TRequest, TResponse> : IPipelineBeh
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>下游处理器的响应结果。</returns>
public async ValueTask<TResponse> Handle(
TRequest message,
MessageHandlerDelegate<TRequest, TResponse> next,
TRequest message, GfCqrs.MessageHandlerDelegate<TRequest, TResponse> next,
CancellationToken cancellationToken)
{
InvocationCount++;
return await next(message, cancellationToken);
}
}
}

View File

@ -347,58 +347,61 @@ public class TestArchitectureContextV3 : IArchitectureContext
{
}
public ValueTask<TResponse> SendRequestAsync<TResponse>(IRequest<TResponse> request,
public ValueTask<TResponse> SendRequestAsync<TResponse>(global::GFramework.Core.Abstractions.Cqrs.IRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public TResponse SendRequest<TResponse>(IRequest<TResponse> request)
public TResponse SendRequest<TResponse>(global::GFramework.Core.Abstractions.Cqrs.IRequest<TResponse> request)
{
throw new NotImplementedException();
}
public ValueTask<TResponse> SendCommandAsync<TResponse>(global::Mediator.ICommand<TResponse> command,
public ValueTask<TResponse> SendCommandAsync<TResponse>(
global::GFramework.Core.Abstractions.Cqrs.Command.ICommand<TResponse> command,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public TResponse SendCommand<TResponse>(global::Mediator.ICommand<TResponse> command)
public TResponse SendCommand<TResponse>(global::GFramework.Core.Abstractions.Cqrs.Command.ICommand<TResponse> command)
{
throw new NotImplementedException();
}
public ValueTask<TResponse> SendQueryAsync<TResponse>(global::Mediator.IQuery<TResponse> query,
public ValueTask<TResponse> SendQueryAsync<TResponse>(
global::GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse> query,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public TResponse SendQuery<TResponse>(global::Mediator.IQuery<TResponse> query)
public TResponse SendQuery<TResponse>(global::GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse> query)
{
throw new NotImplementedException();
}
public ValueTask PublishAsync<TNotification>(TNotification notification,
CancellationToken cancellationToken = default) where TNotification : INotification
CancellationToken cancellationToken = default) where TNotification : global::GFramework.Core.Abstractions.Cqrs.INotification
{
throw new NotImplementedException();
}
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(IStreamRequest<TResponse> request,
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
global::GFramework.Core.Abstractions.Cqrs.IStreamRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public ValueTask SendAsync<TCommand>(TCommand command, CancellationToken cancellationToken = default)
where TCommand : IRequest<Unit>
where TCommand : global::GFramework.Core.Abstractions.Cqrs.IRequest<global::GFramework.Core.Abstractions.Cqrs.Unit>
{
throw new NotImplementedException();
}
public ValueTask<TResponse> SendAsync<TResponse>(IRequest<TResponse> command,
public ValueTask<TResponse> SendAsync<TResponse>(global::GFramework.Core.Abstractions.Cqrs.IRequest<TResponse> command,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
@ -439,4 +442,4 @@ public class TestArchitectureContextV3 : IArchitectureContext
}
}
#endregion
#endregion

View File

@ -394,58 +394,61 @@ public class TestArchitectureContext : IArchitectureContext
{
}
public ValueTask<TResponse> SendRequestAsync<TResponse>(IRequest<TResponse> request,
public ValueTask<TResponse> SendRequestAsync<TResponse>(global::GFramework.Core.Abstractions.Cqrs.IRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public TResponse SendRequest<TResponse>(IRequest<TResponse> request)
public TResponse SendRequest<TResponse>(global::GFramework.Core.Abstractions.Cqrs.IRequest<TResponse> request)
{
throw new NotImplementedException();
}
public ValueTask<TResponse> SendCommandAsync<TResponse>(global::Mediator.ICommand<TResponse> command,
public ValueTask<TResponse> SendCommandAsync<TResponse>(
global::GFramework.Core.Abstractions.Cqrs.Command.ICommand<TResponse> command,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public TResponse SendCommand<TResponse>(global::Mediator.ICommand<TResponse> command)
public TResponse SendCommand<TResponse>(global::GFramework.Core.Abstractions.Cqrs.Command.ICommand<TResponse> command)
{
throw new NotImplementedException();
}
public ValueTask<TResponse> SendQueryAsync<TResponse>(global::Mediator.IQuery<TResponse> query,
public ValueTask<TResponse> SendQueryAsync<TResponse>(
global::GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse> query,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public TResponse SendQuery<TResponse>(global::Mediator.IQuery<TResponse> query)
public TResponse SendQuery<TResponse>(global::GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse> query)
{
throw new NotImplementedException();
}
public ValueTask PublishAsync<TNotification>(TNotification notification,
CancellationToken cancellationToken = default) where TNotification : INotification
CancellationToken cancellationToken = default) where TNotification : global::GFramework.Core.Abstractions.Cqrs.INotification
{
throw new NotImplementedException();
}
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(IStreamRequest<TResponse> request,
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
global::GFramework.Core.Abstractions.Cqrs.IStreamRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public ValueTask SendAsync<TCommand>(TCommand command, CancellationToken cancellationToken = default)
where TCommand : IRequest<Unit>
where TCommand : global::GFramework.Core.Abstractions.Cqrs.IRequest<global::GFramework.Core.Abstractions.Cqrs.Unit>
{
throw new NotImplementedException();
}
public ValueTask<TResponse> SendAsync<TResponse>(IRequest<TResponse> command,
public ValueTask<TResponse> SendAsync<TResponse>(global::GFramework.Core.Abstractions.Cqrs.IRequest<TResponse> command,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
@ -510,4 +513,4 @@ public class TestArchitectureContext : IArchitectureContext
{
return Environment;
}
}
}

View File

@ -0,0 +1,221 @@
using System.Reflection;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Architectures;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Core.Tests.Logging;
namespace GFramework.Core.Tests.Cqrs;
/// <summary>
/// 验证 CQRS 处理器自动注册在顺序与容错层面的可观察行为。
/// </summary>
[TestFixture]
internal sealed class CqrsHandlerRegistrarTests
{
private MicrosoftDiContainer? _container;
private ArchitectureContext? _context;
/// <summary>
/// 初始化测试容器并重置共享状态。
/// </summary>
[SetUp]
public void SetUp()
{
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
DeterministicNotificationHandlerState.Reset();
_container = new MicrosoftDiContainer();
CqrsTestRuntime.RegisterHandlers(
_container,
typeof(CqrsHandlerRegistrarTests).Assembly);
_container.Freeze();
_context = new ArchitectureContext(_container);
}
/// <summary>
/// 清理测试过程中创建的上下文与共享状态。
/// </summary>
[TearDown]
public void TearDown()
{
_context = null;
_container = null;
DeterministicNotificationHandlerState.Reset();
}
/// <summary>
/// 验证自动扫描到的通知处理器会按稳定名称顺序执行,而不是依赖反射枚举顺序。
/// </summary>
[Test]
public async Task PublishAsync_Should_Run_Notification_Handlers_In_Deterministic_Name_Order()
{
await _context!.PublishAsync(new DeterministicOrderNotification());
Assert.That(
DeterministicNotificationHandlerState.InvocationOrder,
Is.EqualTo(
[
nameof(AlphaDeterministicNotificationHandler),
nameof(ZetaDeterministicNotificationHandler)
]));
}
/// <summary>
/// 验证部分类型加载失败时仍能保留可加载类型,并记录诊断日志。
/// </summary>
[Test]
public void RegisterHandlers_Should_Register_Loadable_Types_And_Log_Warnings_When_Assembly_Load_Partially_Fails()
{
var originalProvider = LoggerFactoryResolver.Provider;
var capturingProvider = new CapturingLoggerFactoryProvider(LogLevel.Warning);
var reflectionTypeLoadException = new ReflectionTypeLoadException(
[typeof(AlphaDeterministicNotificationHandler), null],
[new TypeLoadException("Missing optional dependency for registrar test.")]);
var partiallyLoadableAssembly = new Mock<Assembly>();
partiallyLoadableAssembly
.SetupGet(static assembly => assembly.FullName)
.Returns("GFramework.Core.Tests.Cqrs.PartiallyLoadableAssembly, Version=1.0.0.0");
partiallyLoadableAssembly
.Setup(static assembly => assembly.GetTypes())
.Throws(reflectionTypeLoadException);
LoggerFactoryResolver.Provider = capturingProvider;
try
{
var container = new MicrosoftDiContainer();
CqrsTestRuntime.RegisterHandlers(container, partiallyLoadableAssembly.Object);
container.Freeze();
var handlers = container.GetAll<INotificationHandler<DeterministicOrderNotification>>();
var warningLogs = capturingProvider.Loggers
.SelectMany(static logger => logger.Logs)
.Where(static log => log.Level == LogLevel.Warning)
.ToList();
Assert.Multiple(() =>
{
Assert.That(
handlers.Select(static handler => handler.GetType()),
Is.EqualTo([typeof(AlphaDeterministicNotificationHandler)]));
Assert.That(warningLogs.Count, Is.GreaterThanOrEqualTo(2));
Assert.That(
warningLogs.Any(log => log.Message.Contains("partially failed", StringComparison.Ordinal)),
Is.True);
Assert.That(
warningLogs.Any(log =>
log.Message.Contains("Missing optional dependency", StringComparison.Ordinal)),
Is.True);
});
}
finally
{
LoggerFactoryResolver.Provider = originalProvider;
}
}
}
/// <summary>
/// 记录确定性通知处理器的实际执行顺序。
/// </summary>
internal static class DeterministicNotificationHandlerState
{
/// <summary>
/// 获取当前测试中的通知处理器执行顺序。
/// </summary>
public static List<string> InvocationOrder { get; } = [];
/// <summary>
/// 重置共享的执行顺序状态。
/// </summary>
public static void Reset()
{
InvocationOrder.Clear();
}
}
/// <summary>
/// 用于验证同一通知的多个处理器是否按稳定顺序执行。
/// </summary>
internal sealed record DeterministicOrderNotification : INotification;
/// <summary>
/// 故意放在 Alpha 之前声明,用于验证注册器不会依赖源码声明顺序。
/// </summary>
internal sealed class ZetaDeterministicNotificationHandler : INotificationHandler<DeterministicOrderNotification>
{
/// <summary>
/// 记录当前处理器已执行。
/// </summary>
/// <param name="notification">通知实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(DeterministicOrderNotification notification, CancellationToken cancellationToken)
{
DeterministicNotificationHandlerState.InvocationOrder.Add(nameof(ZetaDeterministicNotificationHandler));
return ValueTask.CompletedTask;
}
}
/// <summary>
/// 名称排序上应先于 Zeta 处理器执行的通知处理器。
/// </summary>
internal sealed class AlphaDeterministicNotificationHandler : INotificationHandler<DeterministicOrderNotification>
{
/// <summary>
/// 记录当前处理器已执行。
/// </summary>
/// <param name="notification">通知实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(DeterministicOrderNotification notification, CancellationToken cancellationToken)
{
DeterministicNotificationHandlerState.InvocationOrder.Add(nameof(AlphaDeterministicNotificationHandler));
return ValueTask.CompletedTask;
}
}
/// <summary>
/// 为 CQRS 注册测试捕获真实启动路径中创建的日志记录器。
/// </summary>
/// <remarks>
/// 处理器注册入口会分别为测试运行时、容器和注册器创建日志器。
/// 该提供程序统一保留这些测试日志器,以便断言警告是否经由公开入口真正发出。
/// </remarks>
internal sealed class CapturingLoggerFactoryProvider : ILoggerFactoryProvider
{
private readonly List<TestLogger> _loggers = [];
/// <summary>
/// 使用指定的最小日志级别初始化一个新的捕获型日志工厂提供程序。
/// </summary>
/// <param name="minLevel">要应用到新建测试日志器的最小日志级别。</param>
public CapturingLoggerFactoryProvider(LogLevel minLevel = LogLevel.Info)
{
MinLevel = minLevel;
}
/// <summary>
/// 获取通过当前提供程序创建的全部测试日志器。
/// </summary>
public IReadOnlyList<TestLogger> Loggers => _loggers;
/// <summary>
/// 获取或设置新建测试日志器的最小日志级别。
/// </summary>
public LogLevel MinLevel { get; set; }
/// <summary>
/// 创建一个测试日志器并将其纳入捕获集合。
/// </summary>
/// <param name="name">日志记录器名称。</param>
/// <returns>用于后续断言的测试日志器。</returns>
public ILogger CreateLogger(string name)
{
var logger = new TestLogger(name, MinLevel);
_loggers.Add(logger);
return logger;
}
}

View File

@ -0,0 +1,56 @@
using System.Reflection;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Architectures;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
namespace GFramework.Core.Tests;
/// <summary>
/// 为测试项目提供对 CQRS 处理器真实注册入口的受控访问。
/// </summary>
/// <remarks>
/// 测试应通过该入口驱动注册流程,而不是直接反射调用注册器的私有辅助方法,
/// 这样可以覆盖生产启动路径中的程序集去重、日志记录与容错恢复行为。
/// </remarks>
internal static class CqrsTestRuntime
{
private static readonly Type CqrsHandlerRegistrarType = typeof(ArchitectureContext).Assembly
.GetType(
"GFramework.Core.Cqrs.Internal.CqrsHandlerRegistrar",
throwOnError: true)!
?? throw new InvalidOperationException(
"Failed to locate CqrsHandlerRegistrar type.");
private static readonly MethodInfo RegisterHandlersMethod = CqrsHandlerRegistrarType
.GetMethod(
"RegisterHandlers",
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static,
binder: null,
[
typeof(IIocContainer),
typeof(IEnumerable<Assembly>),
typeof(ILogger)
],
modifiers: null)
?? throw new InvalidOperationException(
"Failed to locate CqrsHandlerRegistrar.RegisterHandlers.");
/// <summary>
/// 通过与生产代码一致的注册入口扫描并注册指定程序集中的 CQRS 处理器。
/// </summary>
/// <param name="container">承载处理器映射的测试容器。</param>
/// <param name="assemblies">要扫描的程序集集合。</param>
internal static void RegisterHandlers(MicrosoftDiContainer container, params Assembly[] assemblies)
{
ArgumentNullException.ThrowIfNull(container);
ArgumentNullException.ThrowIfNull(assemblies);
var logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsTestRuntime));
RegisterHandlersMethod.Invoke(
null,
[container, assemblies.Where(static assembly => assembly is not null).Distinct().ToArray(), logger]);
}
}

View File

@ -1,10 +1,9 @@
using System.Diagnostics;
using System.Reflection;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Architectures;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using Mediator;
using Microsoft.Extensions.DependencyInjection;
namespace GFramework.Core.Tests.Mediator;
@ -15,22 +14,26 @@ namespace GFramework.Core.Tests.Mediator;
[TestFixture]
public class MediatorAdvancedFeaturesTests
{
private MicrosoftDiContainer? _container;
private ArchitectureContext? _context;
[SetUp]
public void SetUp()
{
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
_container = new MicrosoftDiContainer();
TestCircuitBreakerHandler.Reset();
var loggerField = typeof(MicrosoftDiContainer).GetField("_logger",
BindingFlags.NonPublic | BindingFlags.Instance);
loggerField?.SetValue(_container,
LoggerFactoryResolver.Provider.CreateLogger(nameof(MediatorAdvancedFeaturesTests)));
// 注册Mediator及相关处理器
_container.ExecuteServicesHook(configurator =>
{
configurator.AddMediator(options => { options.ServiceLifetime = ServiceLifetime.Singleton; });
});
CqrsTestRuntime.RegisterHandlers(
_container,
typeof(MediatorAdvancedFeaturesTests).Assembly,
typeof(ArchitectureContext).Assembly);
_container.Freeze();
_context = new ArchitectureContext(_container);
@ -43,9 +46,6 @@ public class MediatorAdvancedFeaturesTests
_container = null;
}
private ArchitectureContext? _context;
private MicrosoftDiContainer? _container;
[Test]
public async Task Request_With_Validation_Behavior_Should_Validate_Input()
@ -136,9 +136,6 @@ public class MediatorAdvancedFeaturesTests
[Test]
public async Task Circuit_Breaker_Should_Prevent_Cascading_Failures()
{
TestCircuitBreakerHandler.FailureCount = 0;
TestCircuitBreakerHandler.SuccessCount = 0;
// 先触发几次失败
for (int i = 0; i < 5; i++)
{
@ -276,12 +273,10 @@ public sealed class TestTransientErrorRequestHandler : IRequestHandler<TestTrans
public sealed class TestCircuitBreakerRequestHandler : IRequestHandler<TestCircuitBreakerRequest, string>
{
private static bool _circuitOpen = false;
public ValueTask<string> Handle(TestCircuitBreakerRequest request, CancellationToken cancellationToken)
{
// 检查断路器状态
if (_circuitOpen)
if (TestCircuitBreakerHandler.CircuitOpen)
{
throw new InvalidOperationException("Circuit breaker is open");
}
@ -293,7 +288,7 @@ public sealed class TestCircuitBreakerRequestHandler : IRequestHandler<TestCircu
// 达到阈值后打开断路器
if (TestCircuitBreakerHandler.FailureCount >= 5)
{
_circuitOpen = true;
TestCircuitBreakerHandler.CircuitOpen = true;
}
throw new InvalidOperationException("Service unavailable");
@ -452,6 +447,17 @@ public static class TestCircuitBreakerHandler
{
public static int FailureCount { get; set; }
public static int SuccessCount { get; set; }
public static bool CircuitOpen { get; set; }
/// <summary>
/// 重置断路器测试状态,避免静态字段在测试之间互相污染。
/// </summary>
public static void Reset()
{
FailureCount = 0;
SuccessCount = 0;
CircuitOpen = false;
}
}
public sealed record TestCircuitBreakerRequest : IRequest<string>
@ -487,4 +493,4 @@ public sealed record TestDatabaseRequest : IRequest<string>
public List<string> Storage { get; init; } = new();
}
#endregion
#endregion

View File

@ -1,12 +1,12 @@
using System.Diagnostics;
using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Architectures;
using GFramework.Core.Command;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using Mediator;
using Microsoft.Extensions.DependencyInjection;
using GFramework.Core.Rule;
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
namespace GFramework.Core.Tests.Mediator;
@ -18,11 +18,17 @@ namespace GFramework.Core.Tests.Mediator;
[TestFixture]
public class MediatorArchitectureIntegrationTests
{
private CommandExecutor? _commandBus;
private MicrosoftDiContainer? _container;
private ArchitectureContext? _context;
[SetUp]
public void SetUp()
{
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
_container = new MicrosoftDiContainer();
TestPerDispatchContextAwareHandler.Reset();
var loggerField = typeof(MicrosoftDiContainer).GetField("_logger",
BindingFlags.NonPublic | BindingFlags.Instance);
@ -33,11 +39,10 @@ public class MediatorArchitectureIntegrationTests
_commandBus = new CommandExecutor();
_container.RegisterPlurality(_commandBus);
// 注册Mediator
_container.ExecuteServicesHook(configurator =>
{
configurator.AddMediator(options => { options.ServiceLifetime = ServiceLifetime.Singleton; });
});
CqrsTestRuntime.RegisterHandlers(
_container,
typeof(MediatorArchitectureIntegrationTests).Assembly,
typeof(ArchitectureContext).Assembly);
_container.Freeze();
_context = new ArchitectureContext(_container);
@ -51,10 +56,6 @@ public class MediatorArchitectureIntegrationTests
_commandBus = null;
}
private ArchitectureContext? _context;
private MicrosoftDiContainer? _container;
private CommandExecutor? _commandBus;
[Test]
public async Task Handler_Can_Access_Architecture_Context()
{
@ -292,6 +293,20 @@ public class MediatorArchitectureIntegrationTests
Assert.That(traditionalCommand.Executed, Is.True);
Assert.That(result, Is.EqualTo(42));
}
[Test]
public async Task ContextAware_Handler_Should_Use_A_Fresh_Instance_Per_Request()
{
var firstResult = await _context!.SendRequestAsync(new TestPerDispatchContextAwareRequest());
var secondResult = await _context.SendRequestAsync(new TestPerDispatchContextAwareRequest());
Assert.Multiple(() =>
{
Assert.That(firstResult, Is.Not.EqualTo(secondResult));
Assert.That(TestPerDispatchContextAwareHandler.SeenInstanceIds, Is.EqualTo([firstResult, secondResult]));
Assert.That(TestPerDispatchContextAwareHandler.Contexts, Has.All.SameAs(_context));
});
}
}
#region Integration Test Classes
@ -445,6 +460,42 @@ public sealed class TestMediatorRequestHandler : IRequestHandler<TestMediatorReq
}
}
/// <summary>
/// 用于验证自动扫描到的上下文感知处理器会按请求创建新实例。
/// </summary>
public sealed class TestPerDispatchContextAwareHandler : ContextAwareBase,
IRequestHandler<TestPerDispatchContextAwareRequest, int>
{
private static int _nextInstanceId;
private readonly int _instanceId = Interlocked.Increment(ref _nextInstanceId);
public static List<IArchitectureContext?> Contexts { get; } = [];
public static List<int> SeenInstanceIds { get; } = [];
/// <summary>
/// 记录当前实例编号与收到的架构上下文。
/// </summary>
/// <param name="request">请求实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>当前处理器实例编号。</returns>
public ValueTask<int> Handle(TestPerDispatchContextAwareRequest request, CancellationToken cancellationToken)
{
Contexts.Add(Context);
SeenInstanceIds.Add(_instanceId);
return ValueTask.FromResult(_instanceId);
}
/// <summary>
/// 重置跨测试共享的实例跟踪状态。
/// </summary>
public static void Reset()
{
Contexts.Clear();
SeenInstanceIds.Clear();
_nextInstanceId = 0;
}
}
public sealed record TestContextAwareRequest : IRequest<string>;
public static class TestContextAwareHandler
@ -545,6 +596,11 @@ public sealed record TestMediatorRequest : IRequest<int>
public int Value { get; init; }
}
/// <summary>
/// 用于验证每次请求分发都会获得新的上下文感知处理器实例。
/// </summary>
public sealed record TestPerDispatchContextAwareRequest : IRequest<int>;
// 传统命令用于混合测试
public class TestTraditionalCommand : ICommand
{
@ -559,4 +615,4 @@ public class TestTraditionalCommand : ICommand
public IArchitectureContext GetContext() => null!;
}
#endregion
#endregion

View File

@ -2,6 +2,7 @@ using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Architectures;
using GFramework.Core.Command;
@ -10,22 +11,26 @@ using GFramework.Core.Events;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Core.Query;
using Mediator;
using Microsoft.Extensions.DependencyInjection;
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
// ✅ Mediator 库的命名空间
// ✅ 使用 global using 或别名来区分
using Unit = GFramework.Core.Abstractions.Cqrs.Unit;
namespace GFramework.Core.Tests.Mediator;
[TestFixture]
public class MediatorComprehensiveTests
{
private AsyncQueryExecutor? _asyncQueryBus;
private CommandExecutor? _commandBus;
private MicrosoftDiContainer? _container;
private ArchitectureContext? _context;
private DefaultEnvironment? _environment;
private EventBus? _eventBus;
private QueryExecutor? _queryBus;
/// <summary>
/// 测试初始化方法,在每个测试方法执行前运行。
/// 负责初始化日志工厂、依赖注入容器、Mediator以及各种总线服务。
/// 负责初始化日志工厂、依赖注入容器、自有 CQRS 处理器以及各种总线服务。
/// </summary>
[SetUp]
public void SetUp()
@ -51,13 +56,11 @@ public class MediatorComprehensiveTests
_container.RegisterPlurality(_asyncQueryBus);
_container.RegisterPlurality(_environment);
// ✅ 注册 Mediator
_container.ExecuteServicesHook(configurator =>
{
configurator.AddMediator(options => { options.ServiceLifetime = ServiceLifetime.Singleton; });
});
CqrsTestRuntime.RegisterHandlers(
_container,
typeof(MediatorComprehensiveTests).Assembly,
typeof(ArchitectureContext).Assembly);
// ✅ Freeze 容器
_container.Freeze();
_context = new ArchitectureContext(_container);
@ -79,14 +82,6 @@ public class MediatorComprehensiveTests
_environment = null;
}
private ArchitectureContext? _context;
private MicrosoftDiContainer? _container;
private EventBus? _eventBus;
private CommandExecutor? _commandBus;
private QueryExecutor? _queryBus;
private AsyncQueryExecutor? _asyncQueryBus;
private DefaultEnvironment? _environment;
/// <summary>
/// 测试SendRequestAsync方法在请求有效时返回结果
/// </summary>
@ -194,19 +189,19 @@ public class MediatorComprehensiveTests
/// <summary>
/// 测试未注册的Mediator抛出InvalidOperationException
/// 测试未注册的 CQRS handler 时抛出 InvalidOperationException
/// </summary>
[Test]
public void Unregistered_Mediator_Should_Throw_InvalidOperationException()
public void Unregistered_Cqrs_Handler_Should_Throw_InvalidOperationException()
{
var containerWithoutMediator = new MicrosoftDiContainer();
containerWithoutMediator.Freeze();
var containerWithoutHandlers = new MicrosoftDiContainer();
containerWithoutHandlers.Freeze();
var contextWithoutMediator = new ArchitectureContext(containerWithoutMediator);
var contextWithoutHandlers = new ArchitectureContext(containerWithoutHandlers);
var testRequest = new TestRequest { Value = 42 };
Assert.ThrowsAsync<InvalidOperationException>(async () =>
await contextWithoutMediator.SendRequestAsync(testRequest));
await contextWithoutHandlers.SendRequestAsync(testRequest));
}
/// <summary>
@ -270,10 +265,10 @@ public class MediatorComprehensiveTests
}
/// <summary>
/// 测试并发Mediator请求不会相互干扰
/// 测试并发 CQRS 请求不会相互干扰
/// </summary>
[Test]
public async Task Concurrent_Mediator_Requests_Should_Not_Interfere()
public async Task Concurrent_Cqrs_Requests_Should_Not_Interfere()
{
const int requestCount = 10;
var tasks = new List<Task<int>>();
@ -389,10 +384,10 @@ public class MediatorComprehensiveTests
}
/// <summary>
/// 测试Mediator性能基准
/// 测试 CQRS 性能基准
/// </summary>
[Test]
public async Task Performance_Benchmark_For_Mediator()
public async Task Performance_Benchmark_For_Cqrs()
{
const int iterations = 1000;
var stopwatch = Stopwatch.StartNew();
@ -413,17 +408,17 @@ public class MediatorComprehensiveTests
}
/// <summary>
/// 测试Mediator和传统CQRS可以共存
/// 测试自有 CQRS 和传统 CQRS 可以共存
/// </summary>
[Test]
public async Task Mediator_And_Legacy_CQRS_Can_Coexist()
public async Task Cqrs_And_Legacy_CQRS_Can_Coexist()
{
// 使用传统方式
var legacyCommand = new TestLegacyCommand();
_context!.SendCommand(legacyCommand);
Assert.That(legacyCommand.Executed, Is.True);
// 使用Mediator方式
// 使用自有 CQRS 方式
var mediatorCommand = new TestCommandWithResult { ResultValue = 999 };
var result = await _context.SendAsync(mediatorCommand);
Assert.That(result, Is.EqualTo(999));
@ -434,7 +429,7 @@ public class MediatorComprehensiveTests
}
}
#region Advanced Test Classes for Mediator Features
#region Advanced Test Classes for CQRS Features
public sealed record TestLongRunningRequest : IRequest<string>
{
@ -628,9 +623,9 @@ public class TestLegacyCommand : ICommand
#endregion
#region Test Classes - Mediator ()
#region Test Classes - CQRS Runtime
// ✅ 这些类使用 Mediator.IRequest
// ✅ 这些类使用自有 CQRS IRequest
public sealed record TestRequest : IRequest<int>
{
public int Value { get; init; }
@ -662,7 +657,7 @@ public sealed record TestStreamRequest : IStreamRequest<int>
public int[] Values { get; init; } = [];
}
// ✅ 这些 Handler 使用 Mediator.IRequestHandler
// ✅ 这些 Handler 使用自有 CQRS IRequestHandler
public sealed class TestRequestHandler : IRequestHandler<TestRequest, int>
{
public ValueTask<int> Handle(TestRequest request, CancellationToken cancellationToken)
@ -726,4 +721,4 @@ public sealed class TestStreamRequestHandler : IStreamRequestHandler<TestStreamR
}
}
#endregion
#endregion

View File

@ -1,6 +1,7 @@
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Environment;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Cqrs.Internal;
using Microsoft.Extensions.DependencyInjection;
namespace GFramework.Core.Architectures;
@ -92,16 +93,20 @@ internal sealed class ArchitectureBootstrapper(
/// <summary>
/// 为服务容器设置上下文并执行扩展配置钩子。
/// 这一步统一承接 Mediator 等容器扩展的接入点,避免 <see cref="Architecture" /> 直接操作容器细节。
/// 这一步统一承接 CQRS 运行时与容器扩展的接入点,避免 <see cref="Architecture" /> 直接操作容器细节。
/// </summary>
/// <param name="context">当前架构上下文。</param>
/// <param name="configurator">可选的服务集合配置委托。</param>
private void ConfigureServices(IArchitectureContext context, Action<IServiceCollection>? configurator)
{
services.SetContext(context);
CqrsHandlerRegistrar.RegisterHandlers(
services.Container,
[architectureType.Assembly, typeof(ArchitectureContext).Assembly],
logger);
if (configurator is null)
logger.Debug("Mediator-based cqrs will not take effect without the service setter configured!");
logger.Debug("No external service configurator provided. Using built-in CQRS runtime registration only.");
services.Container.ExecuteServicesHook(configurator);
}
@ -115,4 +120,4 @@ internal sealed class ArchitectureBootstrapper(
{
await services.ModuleManager.InitializeAllAsync(asyncMode);
}
}
}

View File

@ -1,14 +1,19 @@
using System.Collections.Concurrent;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Command;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Cqrs.Command;
using GFramework.Core.Abstractions.Cqrs.Query;
using GFramework.Core.Abstractions.Environment;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Query;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
using Mediator;
using GFramework.Core.Cqrs.Internal;
using GFramework.Core.Logging;
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
namespace GFramework.Core.Architectures;
@ -20,23 +25,15 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
{
private readonly IIocContainer _container = container ?? throw new ArgumentNullException(nameof(container));
private readonly ConcurrentDictionary<Type, object> _serviceCache = new();
private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(ArchitectureContext));
private CqrsDispatcher? _cqrsDispatcher;
#region Mediator Integration
#region CQRS Integration
/// <summary>
/// 获取 Mediator 实例(延迟加载)
/// 获取 CQRS 运行时分发器(延迟初始化)。
/// </summary>
private IMediator Mediator => GetOrCache<IMediator>();
/// <summary>
/// 获取 ISender 实例(更轻量的发送器)
/// </summary>
private ISender Sender => GetOrCache<ISender>();
/// <summary>
/// 获取 IPublisher 实例(用于发布通知)
/// </summary>
private IPublisher Publisher => GetOrCache<IPublisher>();
private CqrsDispatcher CqrsDispatcher => _cqrsDispatcher ??= new CqrsDispatcher(_container, this, _logger);
/// <summary>
/// 获取指定类型的服务实例,如果缓存中存在则直接返回,否则从容器中获取并缓存
@ -64,30 +61,23 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
}
/// <summary>
/// [Mediator] 发送请求Command/Query
/// 这是推荐的新方式,统一处理命令和查询
/// 发送请求Command/Query
/// 使用 GFramework 自有 CQRS runtime 统一处理命令和查询。
/// </summary>
/// <typeparam name="TResponse">响应类型</typeparam>
/// <param name="request">请求对象Command 或 Query</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>响应结果</returns>
/// <exception cref="InvalidOperationException">当 Mediator 未注册时抛出</exception>
public async ValueTask<TResponse> SendRequestAsync<TResponse>(
IRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
var mediator = Mediator;
if (mediator == null)
throw new InvalidOperationException(
"Mediator not registered. Call EnableMediator() in your Architecture.OnInitialize() method.");
return await mediator.Send(request, cancellationToken);
return await CqrsDispatcher.SendAsync(request, cancellationToken);
}
/// <summary>
/// [Mediator] 发送请求的同步版本(不推荐,仅用于兼容性)
/// 发送请求的同步版本(不推荐,仅用于兼容性)
/// </summary>
/// <typeparam name="TResponse">响应类型</typeparam>
/// <param name="request">请求对象</param>
@ -98,8 +88,8 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
}
/// <summary>
/// [Mediator] 发布通知(一对多)
/// 用于事件驱动场景,多个处理器可以同时处理同一个通知
/// 发布通知(一对多)
/// 使用 GFramework 自有 CQRS runtime 分发到所有已注册通知处理器。
/// </summary>
/// <typeparam name="TNotification">通知类型</typeparam>
/// <param name="notification">通知对象</param>
@ -110,16 +100,11 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
where TNotification : INotification
{
ArgumentNullException.ThrowIfNull(notification);
var publisher = Publisher;
if (publisher == null)
throw new InvalidOperationException("Publisher not registered.");
await publisher.Publish(notification, cancellationToken);
await CqrsDispatcher.PublishAsync(notification, cancellationToken);
}
/// <summary>
/// [Mediator] 发送请求并返回流(用于大数据集)
/// 发送请求并返回流(用于大数据集)
/// </summary>
/// <typeparam name="TResponse">响应项类型</typeparam>
/// <param name="request">流式请求</param>
@ -130,12 +115,7 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
var mediator = Mediator;
if (mediator == null)
throw new InvalidOperationException("Mediator not registered.");
return mediator.CreateStream(request, cancellationToken);
return CqrsDispatcher.CreateStream(request, cancellationToken);
}
/// <summary>
@ -180,12 +160,12 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
}
/// <summary>
/// [Mediator] 发送查询的同步版本(不推荐,仅用于兼容性)
/// 发送 CQRS 查询的同步版本(不推荐,仅用于兼容性)
/// </summary>
/// <typeparam name="TResponse">查询响应类型</typeparam>
/// <param name="query">要发送的查询对象</param>
/// <returns>查询结果</returns>
public TResponse SendQuery<TResponse>(Mediator.IQuery<TResponse> query)
public TResponse SendQuery<TResponse>(GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse> query)
{
return SendQueryAsync(query).AsTask().GetAwaiter().GetResult();
}
@ -205,23 +185,17 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
}
/// <summary>
/// [Mediator] 异步发送查询并返回结果
/// 通过Mediator模式发送查询请求支持取消操作
/// 异步发送 CQRS 查询并返回结果。
/// </summary>
/// <typeparam name="TResponse">查询响应类型</typeparam>
/// <param name="query">要发送的查询对象</param>
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>包含查询结果的ValueTask</returns>
public async ValueTask<TResponse> SendQueryAsync<TResponse>(Mediator.IQuery<TResponse> query,
public async ValueTask<TResponse> SendQueryAsync<TResponse>(GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse> query,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(query);
var sender = Sender;
if (sender == null)
throw new InvalidOperationException("Sender not registered.");
return await sender.Send(query, cancellationToken);
return await SendRequestAsync(query, cancellationToken);
}
#endregion
@ -347,23 +321,17 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
#region Command Execution
/// <summary>
/// [Mediator] 异步发送命令并返回结果
/// 通过Mediator模式发送命令请求支持取消操作
/// 异步发送 CQRS 命令并返回结果。
/// </summary>
/// <typeparam name="TResponse">命令响应类型</typeparam>
/// <param name="command">要发送的命令对象</param>
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>包含命令执行结果的ValueTask</returns>
public async ValueTask<TResponse> SendCommandAsync<TResponse>(Mediator.ICommand<TResponse> command,
public async ValueTask<TResponse> SendCommandAsync<TResponse>(GFramework.Core.Abstractions.Cqrs.Command.ICommand<TResponse> command,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(command);
var sender = Sender;
if (sender == null)
throw new InvalidOperationException("Sender not registered.");
return await sender.Send(command, cancellationToken);
return await SendRequestAsync(command, cancellationToken);
}
/// <summary>
@ -393,12 +361,12 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
}
/// <summary>
/// [Mediator] 发送命令的同步版本(不推荐,仅用于兼容性)
/// 发送 CQRS 命令的同步版本(不推荐,仅用于兼容性)
/// </summary>
/// <typeparam name="TResponse">命令响应类型</typeparam>
/// <param name="command">要发送的命令对象</param>
/// <returns>命令执行结果</returns>
public TResponse SendCommand<TResponse>(Mediator.ICommand<TResponse> command)
public TResponse SendCommand<TResponse>(GFramework.Core.Abstractions.Cqrs.Command.ICommand<TResponse> command)
{
return SendCommandAsync(command).AsTask().GetAwaiter().GetResult();
}
@ -491,4 +459,4 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
}
#endregion
}
}

View File

@ -9,13 +9,13 @@ namespace GFramework.Core.Command;
/// </summary>
/// <typeparam name="TInput">命令输入参数类型,必须实现 ICommandInput 接口</typeparam>
/// <param name="input">命令执行所需的输入参数</param>
public abstract class AbstractCommand<TInput>(TInput input) : ContextAwareBase, ICommand
public abstract class AbstractCommand<TInput>(TInput input) : ContextAwareBase, GFramework.Core.Abstractions.Command.ICommand
where TInput : ICommandInput
{
/// <summary>
/// 执行命令的入口方法,实现 ICommand 接口的 Execute 方法
/// </summary>
void ICommand.Execute()
void GFramework.Core.Abstractions.Command.ICommand.Execute()
{
OnExecute(input);
}
@ -25,4 +25,4 @@ public abstract class AbstractCommand<TInput>(TInput input) : ContextAwareBase,
/// </summary>
/// <param name="input">命令执行所需的输入参数</param>
protected abstract void OnExecute(TInput input);
}
}

View File

@ -10,14 +10,14 @@ namespace GFramework.Core.Command;
/// <typeparam name="TInput">命令输入参数类型,必须实现 ICommandInput 接口</typeparam>
/// <typeparam name="TResult">命令执行后返回的结果类型</typeparam>
/// <param name="input">命令执行所需的输入参数</param>
public abstract class AbstractCommand<TInput, TResult>(TInput input) : ContextAwareBase, ICommand<TResult>
public abstract class AbstractCommand<TInput, TResult>(TInput input) : ContextAwareBase, GFramework.Core.Abstractions.Command.ICommand<TResult>
where TInput : ICommandInput
{
/// <summary>
/// 执行命令的入口方法,实现 ICommand{TResult} 接口的 Execute 方法
/// </summary>
/// <returns>命令执行后的结果</returns>
TResult ICommand<TResult>.Execute()
TResult GFramework.Core.Abstractions.Command.ICommand<TResult>.Execute()
{
return OnExecute(input);
}
@ -28,4 +28,4 @@ public abstract class AbstractCommand<TInput, TResult>(TInput input) : ContextAw
/// <param name="input">命令执行所需的输入参数</param>
/// <returns>命令执行后的结果</returns>
protected abstract TResult OnExecute(TInput input);
}
}

View File

@ -12,9 +12,9 @@
// limitations under the License.
using System.Diagnostics;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Logging;
using Mediator;
namespace GFramework.Core.Cqrs.Behaviors;
@ -69,4 +69,4 @@ public sealed class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRe
throw;
}
}
}
}

View File

@ -12,9 +12,9 @@
// limitations under the License.
using System.Diagnostics;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Logging;
using Mediator;
namespace GFramework.Core.Cqrs.Behaviors;
@ -61,4 +61,4 @@ public sealed class PerformanceBehavior<TRequest, TResponse> : IPipelineBehavior
}
}
}
}
}

View File

@ -11,17 +11,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Cqrs.Command;
using GFramework.Core.Rule;
using Mediator;
namespace GFramework.Core.Cqrs.Command;
/// <summary>
/// 抽象命令处理器基类
/// 继承自ContextAwareBase并实现ICommandHandler接口为具体的命令处理器提供基础功能
/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。
/// 框架会在每次分发前注入当前架构上下文,因此派生类可以通过 Context 访问架构级服务。
/// </summary>
/// <typeparam name="TCommand">命令类型</typeparam>
public abstract class AbstractCommandHandler<TCommand> : ContextAwareBase, ICommandHandler<TCommand>
public abstract class AbstractCommandHandler<TCommand> : ContextAwareBase, IRequestHandler<TCommand, Unit>
where TCommand : ICommand<Unit>
{
/// <summary>
@ -36,12 +38,12 @@ public abstract class AbstractCommandHandler<TCommand> : ContextAwareBase, IComm
/// <summary>
/// 抽象命令处理器基类(带返回值版本)
/// 继承自ContextAwareBase并实现ICommandHandler接口,为具体的命令处理器提供基础功能
/// 支持泛型命令和结果类型,实现CQRS模式中的命令处理
/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能
/// 支持泛型命令和结果类型,框架会在每次分发前注入当前架构上下文。
/// </summary>
/// <typeparam name="TCommand">命令类型必须实现ICommand接口</typeparam>
/// <typeparam name="TResult">命令执行结果类型</typeparam>
public abstract class AbstractCommandHandler<TCommand, TResult> : ContextAwareBase, ICommandHandler<TCommand, TResult>
public abstract class AbstractCommandHandler<TCommand, TResult> : ContextAwareBase, IRequestHandler<TCommand, TResult>
where TCommand : ICommand<TResult>
{
/// <summary>
@ -52,4 +54,4 @@ public abstract class AbstractCommandHandler<TCommand, TResult> : ContextAwareBa
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>表示异步操作完成的ValueTask包含命令执行结果</returns>
public abstract ValueTask<TResult> Handle(TCommand command, CancellationToken cancellationToken);
}
}

View File

@ -11,28 +11,37 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Cqrs.Command;
using GFramework.Core.Rule;
using Mediator;
namespace GFramework.Core.Cqrs.Command;
/// <summary>
/// 抽象流式命令处理器基类
/// 继承自ContextAwareBase并实现IStreamCommandHandler接口为具体的流式命令处理器提供基础功能
/// 支持流式处理命令并产生异步可枚举的响应序列
/// 抽象流式命令处理器基类
/// 继承自 <see cref="ContextAwareBase" /> 并实现 <see cref="IStreamRequestHandler{TRequest,TResponse}" />
/// 为具体的流式命令处理器提供基础功能。
/// </summary>
/// <typeparam name="TCommand">流式命令类型必须实现IStreamCommand接口</typeparam>
/// <typeparam name="TResponse">流式命令响应元素类型</typeparam>
/// <typeparam name="TCommand">流式命令类型,必须实现 <see cref="IStreamCommand{TResponse}" />。</typeparam>
/// <typeparam name="TResponse">流式命令响应元素类型。</typeparam>
/// <remarks>
/// 框架会在每次调用 <c>CreateStream</c> 进入实际处理逻辑前,为当前处理器实例注入架构上下文,
/// 因此派生类只能在 <see cref="Handle" /> 执行期间及其返回的异步枚举序列内假定 <c>Context</c> 可用。
/// 默认注册器会将流式命令处理器注册为瞬态服务,以避免同一个上下文感知实例在多个流或并发请求之间复用。
/// 派生类不应缓存处理器实例,也不应把依赖当前上下文的可变状态泄漏到流外部。
/// 传入 <see cref="Handle" /> 的取消令牌同时约束流的创建与后续枚举,
/// 派生类应在启动阶段和每次生成响应前尊重取消请求,避免在调用方停止枚举后继续执行后台工作。
/// </remarks>
public abstract class AbstractStreamCommandHandler<TCommand, TResponse> : ContextAwareBase,
IStreamCommandHandler<TCommand, TResponse>
IStreamRequestHandler<TCommand, TResponse>
where TCommand : IStreamCommand<TResponse>
{
/// <summary>
/// 处理流式命令并返回异步可枚举的响应序列
/// 由具体的流式命令处理器子类实现流式处理逻辑
/// 处理流式命令并返回异步可枚举的响应序列
/// 由具体的流式命令处理器子类实现流式处理逻辑
/// </summary>
/// <param name="command">要处理的流式命令对象</param>
/// <param name="cancellationToken">取消令牌,用于取消流式处理操作</param>
/// <returns>异步可枚举的响应序列,每个元素类型为TResponse</returns>
/// <param name="command">要处理的流式命令对象</param>
/// <param name="cancellationToken">取消令牌,用于取消流式处理操作</param>
/// <returns>异步可枚举的响应序列,每个元素类型为 <typeparamref name="TResponse" />。</returns>
public abstract IAsyncEnumerable<TResponse> Handle(TCommand command, CancellationToken cancellationToken);
}
}

View File

@ -12,7 +12,6 @@
// limitations under the License.
using GFramework.Core.Abstractions.Cqrs.Command;
using Mediator;
namespace GFramework.Core.Cqrs.Command;
@ -29,4 +28,4 @@ public abstract class CommandBase<TInput, TResponse>(TInput input) : ICommand<TR
/// 获取命令的输入数据。
/// </summary>
public TInput Input => input;
}
}

View File

@ -0,0 +1,263 @@
using System.Collections.Concurrent;
using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Abstractions.Rule;
namespace GFramework.Core.Cqrs.Internal;
/// <summary>
/// GFramework 自有 CQRS 运行时分发器。
/// 该类型负责解析请求/通知处理器,并在调用前为上下文感知对象注入当前架构上下文。
/// </summary>
internal sealed class CqrsDispatcher(
IIocContainer container,
IArchitectureContext context,
ILogger logger)
{
private delegate ValueTask<object?> RequestInvoker(object handler, object request, CancellationToken cancellationToken);
private delegate ValueTask<object?> RequestPipelineInvoker(
object handler,
IReadOnlyList<object> behaviors,
object request,
CancellationToken cancellationToken);
private delegate ValueTask NotificationInvoker(object handler, object notification, CancellationToken cancellationToken);
private delegate object StreamInvoker(object handler, object request, CancellationToken cancellationToken);
private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), RequestInvoker> RequestInvokers = new();
private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), RequestPipelineInvoker> RequestPipelineInvokers = new();
private static readonly ConcurrentDictionary<Type, NotificationInvoker> NotificationInvokers = new();
private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), StreamInvoker> StreamInvokers = new();
/// <summary>
/// 发送请求并返回结果。
/// </summary>
/// <typeparam name="TResponse">响应类型。</typeparam>
/// <param name="request">请求对象。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>请求响应。</returns>
public async ValueTask<TResponse> SendAsync<TResponse>(
IRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
var requestType = request.GetType();
var handlerType = typeof(IRequestHandler<,>).MakeGenericType(requestType, typeof(TResponse));
var handler = container.Get(handlerType)
?? throw new InvalidOperationException(
$"No CQRS request handler registered for {requestType.FullName}.");
PrepareHandler(handler);
var behaviorType = typeof(IPipelineBehavior<,>).MakeGenericType(requestType, typeof(TResponse));
var behaviors = container.GetAll(behaviorType);
foreach (var behavior in behaviors)
PrepareHandler(behavior);
if (behaviors.Count == 0)
{
var invoker = RequestInvokers.GetOrAdd(
(requestType, typeof(TResponse)),
static key => CreateRequestInvoker(key.RequestType, key.ResponseType));
var result = await invoker(handler, request, cancellationToken);
return result is null ? default! : (TResponse)result;
}
var pipelineInvoker = RequestPipelineInvokers.GetOrAdd(
(requestType, typeof(TResponse)),
static key => CreateRequestPipelineInvoker(key.RequestType, key.ResponseType));
var pipelineResult = await pipelineInvoker(handler, behaviors, request, cancellationToken);
return pipelineResult is null ? default! : (TResponse)pipelineResult;
}
/// <summary>
/// 发布通知到所有已注册处理器。
/// </summary>
/// <typeparam name="TNotification">通知类型。</typeparam>
/// <param name="notification">通知对象。</param>
/// <param name="cancellationToken">取消令牌。</param>
public async ValueTask PublishAsync<TNotification>(
TNotification notification,
CancellationToken cancellationToken = default)
where TNotification : INotification
{
ArgumentNullException.ThrowIfNull(notification);
var notificationType = notification.GetType();
var handlerType = typeof(INotificationHandler<>).MakeGenericType(notificationType);
var handlers = container.GetAll(handlerType);
if (handlers.Count == 0)
{
logger.Debug($"No CQRS notification handler registered for {notificationType.FullName}.");
return;
}
var invoker = NotificationInvokers.GetOrAdd(
notificationType,
CreateNotificationInvoker);
foreach (var handler in handlers)
{
PrepareHandler(handler);
await invoker(handler, notification, cancellationToken);
}
}
/// <summary>
/// 创建流式请求并返回异步响应序列。
/// </summary>
/// <typeparam name="TResponse">响应元素类型。</typeparam>
/// <param name="request">流式请求对象。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>异步响应序列。</returns>
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
IStreamRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
var requestType = request.GetType();
var handlerType = typeof(IStreamRequestHandler<,>).MakeGenericType(requestType, typeof(TResponse));
var handler = container.Get(handlerType)
?? throw new InvalidOperationException(
$"No CQRS stream handler registered for {requestType.FullName}.");
PrepareHandler(handler);
var invoker = StreamInvokers.GetOrAdd(
(requestType, typeof(TResponse)),
static key => CreateStreamInvoker(key.RequestType, key.ResponseType));
return (IAsyncEnumerable<TResponse>)invoker(handler, request, cancellationToken);
}
/// <summary>
/// 为上下文感知处理器注入当前架构上下文。
/// </summary>
/// <param name="handler">处理器实例。</param>
private void PrepareHandler(object handler)
{
if (handler is IContextAware contextAware)
contextAware.SetContext(context);
}
/// <summary>
/// 生成请求处理器调用委托,避免每次发送都重复反射。
/// </summary>
private static RequestInvoker CreateRequestInvoker(Type requestType, Type responseType)
{
var method = typeof(CqrsDispatcher)
.GetMethod(nameof(InvokeRequestHandlerAsync), BindingFlags.NonPublic | BindingFlags.Static)!
.MakeGenericMethod(requestType, responseType);
return (RequestInvoker)Delegate.CreateDelegate(typeof(RequestInvoker), method);
}
/// <summary>
/// 生成带管道行为的请求处理委托,避免每次发送都重复反射。
/// </summary>
private static RequestPipelineInvoker CreateRequestPipelineInvoker(Type requestType, Type responseType)
{
var method = typeof(CqrsDispatcher)
.GetMethod(nameof(InvokeRequestPipelineAsync), BindingFlags.NonPublic | BindingFlags.Static)!
.MakeGenericMethod(requestType, responseType);
return (RequestPipelineInvoker)Delegate.CreateDelegate(typeof(RequestPipelineInvoker), method);
}
/// <summary>
/// 生成通知处理器调用委托,避免每次发布都重复反射。
/// </summary>
private static NotificationInvoker CreateNotificationInvoker(Type notificationType)
{
var method = typeof(CqrsDispatcher)
.GetMethod(nameof(InvokeNotificationHandlerAsync), BindingFlags.NonPublic | BindingFlags.Static)!
.MakeGenericMethod(notificationType);
return (NotificationInvoker)Delegate.CreateDelegate(typeof(NotificationInvoker), method);
}
/// <summary>
/// 生成流式处理器调用委托,避免每次创建流都重复反射。
/// </summary>
private static StreamInvoker CreateStreamInvoker(Type requestType, Type responseType)
{
var method = typeof(CqrsDispatcher)
.GetMethod(nameof(InvokeStreamHandler), BindingFlags.NonPublic | BindingFlags.Static)!
.MakeGenericMethod(requestType, responseType);
return (StreamInvoker)Delegate.CreateDelegate(typeof(StreamInvoker), method);
}
/// <summary>
/// 执行已强类型化的请求处理器调用。
/// </summary>
private static async ValueTask<object?> InvokeRequestHandlerAsync<TRequest, TResponse>(
object handler,
object request,
CancellationToken cancellationToken)
where TRequest : IRequest<TResponse>
{
var typedHandler = (IRequestHandler<TRequest, TResponse>)handler;
var typedRequest = (TRequest)request;
var result = await typedHandler.Handle(typedRequest, cancellationToken);
return result;
}
/// <summary>
/// 执行包含管道行为链的请求处理。
/// </summary>
private static async ValueTask<object?> InvokeRequestPipelineAsync<TRequest, TResponse>(
object handler,
IReadOnlyList<object> behaviors,
object request,
CancellationToken cancellationToken)
where TRequest : IRequest<TResponse>
{
var typedHandler = (IRequestHandler<TRequest, TResponse>)handler;
var typedRequest = (TRequest)request;
MessageHandlerDelegate<TRequest, TResponse> next =
(message, token) => typedHandler.Handle(message, token);
for (var i = behaviors.Count - 1; i >= 0; i--)
{
var behavior = (IPipelineBehavior<TRequest, TResponse>)behaviors[i];
var currentNext = next;
next = (message, token) => behavior.Handle(message, currentNext, token);
}
var result = await next(typedRequest, cancellationToken);
return result;
}
/// <summary>
/// 执行已强类型化的通知处理器调用。
/// </summary>
private static ValueTask InvokeNotificationHandlerAsync<TNotification>(
object handler,
object notification,
CancellationToken cancellationToken)
where TNotification : INotification
{
var typedHandler = (INotificationHandler<TNotification>)handler;
var typedNotification = (TNotification)notification;
return typedHandler.Handle(typedNotification, cancellationToken);
}
/// <summary>
/// 执行已强类型化的流式处理器调用。
/// </summary>
private static object InvokeStreamHandler<TRequest, TResponse>(
object handler,
object request,
CancellationToken cancellationToken)
where TRequest : IStreamRequest<TResponse>
{
var typedHandler = (IStreamRequestHandler<TRequest, TResponse>)handler;
var typedRequest = (TRequest)request;
return typedHandler.Handle(typedRequest, cancellationToken);
}
}

View File

@ -0,0 +1,145 @@
using System.Reflection;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Logging;
namespace GFramework.Core.Cqrs.Internal;
/// <summary>
/// 在架构初始化期间扫描并注册 CQRS 处理器。
/// 首批实现采用运行时反射扫描,优先满足“无需 AddMediator 即可工作”的迁移目标。
/// </summary>
internal static class CqrsHandlerRegistrar
{
/// <summary>
/// 扫描指定程序集并注册所有 CQRS 请求/通知/流式处理器。
/// </summary>
/// <param name="container">目标容器。</param>
/// <param name="assemblies">要扫描的程序集集合。</param>
/// <param name="logger">日志记录器。</param>
public static void RegisterHandlers(
IIocContainer container,
IEnumerable<Assembly> assemblies,
ILogger logger)
{
ArgumentNullException.ThrowIfNull(container);
ArgumentNullException.ThrowIfNull(assemblies);
ArgumentNullException.ThrowIfNull(logger);
foreach (var assembly in assemblies
.Where(static assembly => assembly is not null)
.Distinct()
.OrderBy(GetAssemblySortKey, StringComparer.Ordinal))
{
RegisterAssemblyHandlers(container.GetServicesUnsafe, assembly, logger);
}
}
/// <summary>
/// 注册单个程序集里的所有 CQRS 处理器映射。
/// </summary>
private static void RegisterAssemblyHandlers(IServiceCollection services, Assembly assembly, ILogger logger)
{
foreach (var implementationType in GetLoadableTypes(assembly, logger).Where(IsConcreteHandlerType))
{
var handlerInterfaces = implementationType
.GetInterfaces()
.Where(IsSupportedHandlerInterface)
.OrderBy(GetTypeSortKey, StringComparer.Ordinal)
.ToList();
if (handlerInterfaces.Count == 0)
continue;
foreach (var handlerInterface in handlerInterfaces)
{
// Request/notification handlers receive context injection before every dispatch.
// Transient registration avoids sharing mutable Context across concurrent requests.
services.AddTransient(handlerInterface, implementationType);
logger.Debug(
$"Registered CQRS handler {implementationType.FullName} as {handlerInterface.FullName}.");
}
}
}
/// <summary>
/// 安全获取程序集中的可加载类型,并在部分类型加载失败时保留其余处理器注册能力。
/// </summary>
private static IReadOnlyList<Type> GetLoadableTypes(Assembly assembly, ILogger logger)
{
try
{
return assembly.GetTypes()
.Where(static type => type is not null)
.OrderBy(GetTypeSortKey, StringComparer.Ordinal)
.ToList();
}
catch (ReflectionTypeLoadException exception)
{
return RecoverLoadableTypes(assembly, exception, logger);
}
}
/// <summary>
/// 记录部分类型加载失败,并返回仍然可用的类型集合。
/// </summary>
private static IReadOnlyList<Type> RecoverLoadableTypes(
Assembly assembly,
ReflectionTypeLoadException exception,
ILogger logger)
{
var assemblyName = GetAssemblySortKey(assembly);
logger.Warn(
$"CQRS handler scan partially failed for assembly {assemblyName}. Continuing with loadable types.");
foreach (var loaderException in exception.LoaderExceptions.Where(static ex => ex is not null))
{
logger.Warn(
$"Failed to load one or more types while scanning {assemblyName}: {loaderException!.Message}");
}
return exception.Types
.Where(static type => type is not null)
.Cast<Type>()
.OrderBy(GetTypeSortKey, StringComparer.Ordinal)
.ToList();
}
/// <summary>
/// 判断指定类型是否可作为可实例化处理器。
/// </summary>
private static bool IsConcreteHandlerType(Type type)
{
return type is { IsAbstract: false, IsInterface: false } && !type.ContainsGenericParameters;
}
/// <summary>
/// 判断接口是否为当前运行时支持的 CQRS 处理器接口。
/// </summary>
private static bool IsSupportedHandlerInterface(Type type)
{
if (!type.IsGenericType)
return false;
var definition = type.GetGenericTypeDefinition();
return definition == typeof(IRequestHandler<,>) ||
definition == typeof(INotificationHandler<>) ||
definition == typeof(IStreamRequestHandler<,>);
}
/// <summary>
/// 生成程序集排序键,保证跨运行环境的处理器注册顺序稳定。
/// </summary>
private static string GetAssemblySortKey(Assembly assembly)
{
return assembly.FullName ?? assembly.GetName().Name ?? assembly.ToString();
}
/// <summary>
/// 生成类型排序键,保证同一程序集内的处理器与接口映射顺序稳定。
/// </summary>
private static string GetTypeSortKey(Type type)
{
return type.FullName ?? type.Name;
}
}

View File

@ -12,7 +12,7 @@
// limitations under the License.
using GFramework.Core.Rule;
using Mediator;
using GFramework.Core.Abstractions.Cqrs;
namespace GFramework.Core.Cqrs.Notification;
@ -33,4 +33,4 @@ public abstract class AbstractNotificationHandler<TNotification> : ContextAwareB
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>表示异步操作完成的ValueTask</returns>
public abstract ValueTask Handle(TNotification notification, CancellationToken cancellationToken);
}
}

View File

@ -12,7 +12,7 @@
// limitations under the License.
using GFramework.Core.Abstractions.Cqrs.Notification;
using Mediator;
using GFramework.Core.Abstractions.Cqrs;
namespace GFramework.Core.Cqrs.Notification;
@ -28,4 +28,4 @@ public abstract class NotificationBase<TInput>(TInput input) : INotification whe
/// 获取通知的输入数据。
/// </summary>
public TInput Input => input;
}
}

View File

@ -11,19 +11,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Cqrs.Query;
using GFramework.Core.Rule;
using Mediator;
namespace GFramework.Core.Cqrs.Query;
/// <summary>
/// 抽象查询处理器基类
/// 继承自ContextAwareBase并实现IQueryHandler接口,为具体的查询处理器提供基础功能
/// 支持泛型查询和结果类型实现CQRS模式中的查询处理
/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的查询处理器提供基础功能
/// 框架会在每次分发前注入当前架构上下文,因此派生类可以通过 Context 访问架构级服务。
/// </summary>
/// <typeparam name="TQuery">查询类型必须实现IQuery接口</typeparam>
/// <typeparam name="TResult">查询结果类型</typeparam>
public abstract class AbstractQueryHandler<TQuery, TResult> : ContextAwareBase, IQueryHandler<TQuery, TResult>
public abstract class AbstractQueryHandler<TQuery, TResult> : ContextAwareBase, IRequestHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
{
/// <summary>
@ -34,4 +35,4 @@ public abstract class AbstractQueryHandler<TQuery, TResult> : ContextAwareBase,
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>表示异步操作完成的ValueTask包含查询结果</returns>
public abstract ValueTask<TResult> Handle(TQuery query, CancellationToken cancellationToken);
}
}

View File

@ -12,7 +12,8 @@
// limitations under the License.
using GFramework.Core.Rule;
using Mediator;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Cqrs.Query;
namespace GFramework.Core.Cqrs.Query;
@ -24,7 +25,7 @@ namespace GFramework.Core.Cqrs.Query;
/// <typeparam name="TQuery">流式查询类型必须实现IStreamQuery接口</typeparam>
/// <typeparam name="TResponse">流式查询响应元素类型</typeparam>
public abstract class AbstractStreamQueryHandler<TQuery, TResponse> : ContextAwareBase,
IStreamQueryHandler<TQuery, TResponse>
IStreamRequestHandler<TQuery, TResponse>
where TQuery : IStreamQuery<TResponse>
{
/// <summary>
@ -35,4 +36,4 @@ public abstract class AbstractStreamQueryHandler<TQuery, TResponse> : ContextAwa
/// <param name="cancellationToken">取消令牌,用于取消流式查询操作</param>
/// <returns>异步可枚举的响应序列每个元素类型为TResponse</returns>
public abstract IAsyncEnumerable<TResponse> Handle(TQuery query, CancellationToken cancellationToken);
}
}

View File

@ -12,13 +12,12 @@
// limitations under the License.
using GFramework.Core.Abstractions.Cqrs.Query;
using Mediator;
namespace GFramework.Core.Cqrs.Query;
/// <summary>
/// 表示一个基础查询类,用于处理带有输入和响应的查询模式实现。
/// 该类继承自 Mediator.IQuery&lt;TResponse&gt; 接口,提供了通用的查询结构。
/// 该类实现 IQuery&lt;TResponse&gt; 接口,提供了通用的查询结构。
/// </summary>
/// <typeparam name="TInput">查询输入数据的类型,必须实现 IQueryInput 接口</typeparam>
/// <typeparam name="TResponse">查询执行后返回结果的类型</typeparam>
@ -29,4 +28,4 @@ public abstract class QueryBase<TInput, TResponse>(TInput input) : IQuery<TRespo
/// 获取查询的输入数据。
/// </summary>
public TInput Input => input;
}
}

View File

@ -12,7 +12,7 @@
// limitations under the License.
using GFramework.Core.Rule;
using Mediator;
using GFramework.Core.Abstractions.Cqrs;
namespace GFramework.Core.Cqrs.Request;
@ -21,7 +21,7 @@ namespace GFramework.Core.Cqrs.Request;
/// 继承自ContextAwareBase并实现IRequestHandler接口
/// </summary>
/// <typeparam name="TRequest">请求类型必须实现IRequest[Unit]接口</typeparam>
public abstract class AbstractRequestHandler<TRequest> : ContextAwareBase, IRequestHandler<TRequest>
public abstract class AbstractRequestHandler<TRequest> : ContextAwareBase, IRequestHandler<TRequest, Unit>
where TRequest : IRequest<Unit>
{
/// <summary>
@ -49,4 +49,4 @@ public abstract class AbstractRequestHandler<TRequest, TResponse> : ContextAware
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>表示异步操作的ValueTask完成时返回处理结果</returns>
public abstract ValueTask<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}
}

View File

@ -12,7 +12,7 @@
// limitations under the License.
using GFramework.Core.Rule;
using Mediator;
using GFramework.Core.Abstractions.Cqrs;
namespace GFramework.Core.Cqrs.Request;
@ -35,4 +35,4 @@ public abstract class AbstractStreamRequestHandler<TRequest, TResponse> : Contex
/// <param name="cancellationToken">取消令牌,用于取消流式请求操作</param>
/// <returns>异步可枚举的响应序列每个元素类型为TResponse</returns>
public abstract IAsyncEnumerable<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}
}

View File

@ -12,7 +12,7 @@
// limitations under the License.
using GFramework.Core.Abstractions.Cqrs.Request;
using Mediator;
using GFramework.Core.Abstractions.Cqrs;
namespace GFramework.Core.Cqrs.Request;
@ -29,4 +29,4 @@ public abstract class RequestBase<TInput, TResponse>(TInput input) : IRequest<TR
/// 获取请求的输入数据。
/// </summary>
public TInput Input => input;
}
}

View File

@ -1,16 +1,15 @@
using GFramework.Core.Abstractions.Rule;
using Mediator;
using GFramework.Core.Abstractions.Cqrs.Command;
namespace GFramework.Core.Extensions;
/// <summary>
/// 提供对 IContextAware 接口的 Mediator 命令扩展方法
/// 使用 Mediator 库的命令模式
/// 提供对 IContextAware 接口的 CQRS 命令扩展方法。
/// </summary>
public static class ContextAwareMediatorCommandExtensions
{
/// <summary>
/// [Mediator] 发送命令的同步版本(不推荐,仅用于兼容性)
/// 发送命令的同步版本(不推荐,仅用于兼容性)
/// </summary>
/// <typeparam name="TResponse">命令响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
@ -28,7 +27,7 @@ public static class ContextAwareMediatorCommandExtensions
}
/// <summary>
/// [Mediator] 异步发送命令并返回结果
/// 异步发送命令并返回结果
/// </summary>
/// <typeparam name="TResponse">命令响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
@ -45,4 +44,4 @@ public static class ContextAwareMediatorCommandExtensions
var context = contextAware.GetContext();
return context.SendCommandAsync(command, cancellationToken);
}
}
}

View File

@ -1,10 +1,10 @@
using GFramework.Core.Abstractions.Rule;
using Mediator;
using GFramework.Core.Abstractions.Cqrs;
namespace GFramework.Core.Extensions;
/// <summary>
/// 提供对 IContextAware 接口的 Mediator 统一接口扩展方法
/// 提供对 IContextAware 接口的 CQRS 统一接口扩展方法。
/// </summary>
public static class ContextAwareMediatorExtensions
{
@ -122,4 +122,4 @@ public static class ContextAwareMediatorExtensions
var context = contextAware.GetContext();
return context.SendAsync(command, cancellationToken);
}
}
}

View File

@ -1,16 +1,15 @@
using GFramework.Core.Abstractions.Rule;
using Mediator;
using GFramework.Core.Abstractions.Cqrs.Query;
namespace GFramework.Core.Extensions;
/// <summary>
/// 提供对 IContextAware 接口的 Mediator 查询扩展方法
/// 使用 Mediator 库的查询模式
/// 提供对 IContextAware 接口的 CQRS 查询扩展方法。
/// </summary>
public static class ContextAwareMediatorQueryExtensions
{
/// <summary>
/// [Mediator] 发送查询的同步版本(不推荐,仅用于兼容性)
/// 发送查询的同步版本(不推荐,仅用于兼容性)
/// </summary>
/// <typeparam name="TResponse">查询响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
@ -27,7 +26,7 @@ public static class ContextAwareMediatorQueryExtensions
}
/// <summary>
/// [Mediator] 异步发送查询并返回结果
/// 异步发送查询并返回结果
/// </summary>
/// <typeparam name="TResponse">查询响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
@ -44,4 +43,4 @@ public static class ContextAwareMediatorQueryExtensions
var context = contextAware.GetContext();
return context.SendQueryAsync(query, cancellationToken);
}
}
}

View File

@ -1,10 +1,10 @@
using GFramework.Core.Abstractions.Bases;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Logging;
using GFramework.Core.Rule;
using Mediator;
using Microsoft.Extensions.DependencyInjection;
namespace GFramework.Core.Ioc;
@ -804,4 +804,4 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
}
#endregion
}
}

View File

@ -9,7 +9,7 @@ namespace GFramework.Core.Query;
/// </summary>
/// <typeparam name="TInput">查询输入参数的类型必须实现IQueryInput接口</typeparam>
/// <typeparam name="TResult">查询结果的类型</typeparam>
public abstract class AbstractQuery<TInput, TResult>(TInput input) : ContextAwareBase, IQuery<TResult>
public abstract class AbstractQuery<TInput, TResult>(TInput input) : ContextAwareBase, GFramework.Core.Abstractions.Query.IQuery<TResult>
where TInput : IQueryInput
{
/// <summary>
@ -27,4 +27,4 @@ public abstract class AbstractQuery<TInput, TResult>(TInput input) : ContextAwar
/// <param name="input">查询输入参数</param>
/// <returns>查询结果类型为TResult</returns>
protected abstract TResult OnDo(TInput input);
}
}

View File

@ -18,9 +18,17 @@
</PropertyGroup>
<ItemGroup>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Godot.SourceGenerators.dll"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Godot.SourceGenerators.Abstractions.dll"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll"/>
<!--
仅在 NuGet 打包布局存在时自动注入 analyzer。
仓库内项目引用场景会通过 ProjectReference(OutputItemType=Analyzer) 提供生成器,
因此这里需要避免对不存在的打包路径做无效引用。
-->
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Godot.SourceGenerators.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Godot.SourceGenerators.dll')"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Godot.SourceGenerators.Abstractions.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Godot.SourceGenerators.Abstractions.dll')"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll')"/>
</ItemGroup>
<ItemGroup Condition="Exists('$(MSBuildProjectDirectory)/$(GFrameworkGodotProjectFile)')">

View File

@ -1,8 +1,10 @@
using GFramework.Core.Abstractions.Rule;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Cqrs.Command;
using GFramework.Core.Abstractions.Cqrs.Query;
using GFramework.Core.Abstractions.Rule;
using GFramework.Core.Coroutine;
using GFramework.Core.Coroutine.Extensions;
using GFramework.Core.Extensions;
using Mediator;
namespace GFramework.Godot.Coroutine;
@ -104,4 +106,4 @@ public static class ContextAwareCoroutineExtensions
.ToCoroutineEnumerator()
.RunCoroutine(segment, tag);
}
}
}