mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-06 16:16:44 +08:00
feat(cqrs): 添加CQRS处理器基类和测试运行时支持
- 引入AbstractCommandHandler、AbstractQueryHandler等各类处理器基类 - 实现CqrsContextAwareHandlerBase提供上下文感知功能 - 添加CqrsTestRuntime为测试项目提供CQRS运行时访问入口 - 创建AbstractCqrsHandlerContextTests验证上下文注入行为 - 支持命令、查询、通知及流式处理的各种抽象基类实现
This commit is contained in:
parent
a80ff59631
commit
e36c80229a
@ -0,0 +1,74 @@
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
using GFramework.Cqrs.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Cqrs.Tests.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 验证 CQRS handler 基类在脱离 dispatcher 使用时会显式失败,并在注入上下文后保持可观察行为。
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
internal sealed class AbstractCqrsHandlerContextTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证新的轻量 handler 基类不会再偷偷回退到全局 GameContext。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void GetContext_Should_Throw_When_Handler_Has_Not_Been_Initialized_By_Runtime()
|
||||
{
|
||||
var handler = new TestCommandHandler();
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => ((IContextAware)handler).GetContext());
|
||||
|
||||
Assert.That(
|
||||
exception!.Message,
|
||||
Does.Contain("has not been initialized").IgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 runtime 注入上下文后,派生 handler 可以继续访问 Context 并收到 OnContextReady 回调。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Handle_Should_Observe_Injected_Context_And_OnContextReady_Callback()
|
||||
{
|
||||
var handler = new TestCommandHandler();
|
||||
var context = new Mock<IArchitectureContext>(MockBehavior.Strict).Object;
|
||||
|
||||
((IContextAware)handler).SetContext(context);
|
||||
await handler.Handle(new TestCommand(), CancellationToken.None);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(handler.OnContextReadyCallCount, Is.EqualTo(1));
|
||||
Assert.That(handler.LastObservedContext, Is.SameAs(context));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于验证上下文注入行为的最小 CQRS 命令。
|
||||
/// </summary>
|
||||
private sealed record TestCommand : ICommand<Unit>;
|
||||
|
||||
/// <summary>
|
||||
/// 暴露基类上下文访问与初始化回调的测试处理器。
|
||||
/// </summary>
|
||||
private sealed class TestCommandHandler : AbstractCommandHandler<TestCommand>
|
||||
{
|
||||
public int OnContextReadyCallCount { get; private set; }
|
||||
|
||||
public IArchitectureContext? LastObservedContext { get; private set; }
|
||||
|
||||
protected override void OnContextReady()
|
||||
{
|
||||
OnContextReadyCallCount++;
|
||||
}
|
||||
|
||||
public override ValueTask<Unit> Handle(TestCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
LastObservedContext = Context;
|
||||
return ValueTask.FromResult(Unit.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,19 +11,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Command;
|
||||
namespace GFramework.Cqrs.Cqrs.Command;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象命令处理器基类
|
||||
/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。
|
||||
/// 继承自轻量 CQRS 上下文基类并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。
|
||||
/// 框架会在每次分发前注入当前架构上下文,因此派生类可以通过 Context 访问架构级服务。
|
||||
/// </summary>
|
||||
/// <typeparam name="TCommand">命令类型</typeparam>
|
||||
public abstract class AbstractCommandHandler<TCommand> : ContextAwareBase, IRequestHandler<TCommand, Unit>
|
||||
public abstract class AbstractCommandHandler<TCommand> : CqrsContextAwareHandlerBase, IRequestHandler<TCommand, Unit>
|
||||
where TCommand : ICommand<Unit>
|
||||
{
|
||||
/// <summary>
|
||||
@ -38,12 +37,13 @@ public abstract class AbstractCommandHandler<TCommand> : ContextAwareBase, IRequ
|
||||
|
||||
/// <summary>
|
||||
/// 抽象命令处理器基类(带返回值版本)
|
||||
/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。
|
||||
/// 继承自轻量 CQRS 上下文基类并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。
|
||||
/// 支持泛型命令和结果类型,框架会在每次分发前注入当前架构上下文。
|
||||
/// </summary>
|
||||
/// <typeparam name="TCommand">命令类型,必须实现ICommand接口</typeparam>
|
||||
/// <typeparam name="TResult">命令执行结果类型</typeparam>
|
||||
public abstract class AbstractCommandHandler<TCommand, TResult> : ContextAwareBase, IRequestHandler<TCommand, TResult>
|
||||
public abstract class AbstractCommandHandler<TCommand, TResult> : CqrsContextAwareHandlerBase,
|
||||
IRequestHandler<TCommand, TResult>
|
||||
where TCommand : ICommand<TResult>
|
||||
{
|
||||
/// <summary>
|
||||
@ -11,15 +11,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Command;
|
||||
namespace GFramework.Cqrs.Cqrs.Command;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象流式命令处理器基类。
|
||||
/// 继承自 <see cref="ContextAwareBase" /> 并实现 <see cref="IStreamRequestHandler{TRequest,TResponse}" />,
|
||||
/// 继承自轻量 CQRS 上下文基类并实现 <see cref="IStreamRequestHandler{TRequest,TResponse}" />,
|
||||
/// 为具体的流式命令处理器提供基础功能。
|
||||
/// </summary>
|
||||
/// <typeparam name="TCommand">流式命令类型,必须实现 <see cref="IStreamCommand{TResponse}" />。</typeparam>
|
||||
@ -32,7 +31,7 @@ namespace GFramework.Core.Cqrs.Command;
|
||||
/// 传入 <see cref="Handle" /> 的取消令牌同时约束流的创建与后续枚举,
|
||||
/// 派生类应在启动阶段和每次生成响应前尊重取消请求,避免在调用方停止枚举后继续执行后台工作。
|
||||
/// </remarks>
|
||||
public abstract class AbstractStreamCommandHandler<TCommand, TResponse> : ContextAwareBase,
|
||||
public abstract class AbstractStreamCommandHandler<TCommand, TResponse> : CqrsContextAwareHandlerBase,
|
||||
IStreamRequestHandler<TCommand, TResponse>
|
||||
where TCommand : IStreamCommand<TResponse>
|
||||
{
|
||||
59
GFramework.Cqrs/Cqrs/CqrsContextAwareHandlerBase.cs
Normal file
59
GFramework.Cqrs/Cqrs/CqrsContextAwareHandlerBase.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
|
||||
namespace GFramework.Cqrs.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 为 CQRS 处理器提供最小化的上下文感知基类实现。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该基类只承接 CQRS runtime 在分发前注入的 <see cref="IArchitectureContext" />,
|
||||
/// 不再像 <c>ContextAwareBase</c> 那样回退到 <c>GameContext</c> 全局查找。
|
||||
/// 这样可以让 <c>GFramework.Cqrs</c> 保持对 <c>GFramework.Core</c> 运行时实现的零依赖,
|
||||
/// 同时在处理器被错误地脱离 dispatcher 使用时以显式异常快速失败。
|
||||
/// </remarks>
|
||||
public abstract class CqrsContextAwareHandlerBase : IContextAware
|
||||
{
|
||||
private IArchitectureContext? _context;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前分发周期内已注入的架构上下文。
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// 当前处理器尚未被 CQRS runtime 注入上下文。
|
||||
/// </exception>
|
||||
protected IArchitectureContext Context => _context ?? throw new InvalidOperationException(
|
||||
"The CQRS handler context has not been initialized. Ensure the handler is invoked through the CQRS runtime.");
|
||||
|
||||
/// <summary>
|
||||
/// 由 runtime 在分发前注入当前架构上下文。
|
||||
/// </summary>
|
||||
/// <param name="context">当前架构上下文。</param>
|
||||
void IContextAware.SetContext(IArchitectureContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
_context = context;
|
||||
OnContextReady();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前处理器实例已绑定的架构上下文。
|
||||
/// </summary>
|
||||
/// <returns>当前分发周期内的架构上下文。</returns>
|
||||
IArchitectureContext IContextAware.GetContext()
|
||||
{
|
||||
return Context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当上下文注入完成后执行额外初始化。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该钩子保留与旧 <c>ContextAwareBase</c> 相近的扩展点,
|
||||
/// 便于处理器在迁移后继续承接分发前的派生类初始化逻辑。
|
||||
/// </remarks>
|
||||
protected virtual void OnContextReady()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -11,18 +11,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Notification;
|
||||
namespace GFramework.Cqrs.Cqrs.Notification;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象通知处理器基类
|
||||
/// 继承自ContextAwareBase并实现INotificationHandler接口,为具体的通知处理器提供基础功能
|
||||
/// 继承自轻量 CQRS 上下文基类并实现INotificationHandler接口,为具体的通知处理器提供基础功能
|
||||
/// 用于处理CQRS模式中的通知消息,支持异步处理
|
||||
/// </summary>
|
||||
/// <typeparam name="TNotification">通知类型,必须实现INotification接口</typeparam>
|
||||
public abstract class AbstractNotificationHandler<TNotification> : ContextAwareBase, INotificationHandler<TNotification>
|
||||
public abstract class AbstractNotificationHandler<TNotification> : CqrsContextAwareHandlerBase,
|
||||
INotificationHandler<TNotification>
|
||||
where TNotification : INotification
|
||||
{
|
||||
/// <summary>
|
||||
@ -11,20 +11,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Query;
|
||||
namespace GFramework.Cqrs.Cqrs.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象查询处理器基类
|
||||
/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的查询处理器提供基础功能。
|
||||
/// 继承自轻量 CQRS 上下文基类并实现 IRequestHandler 接口,为具体的查询处理器提供基础功能。
|
||||
/// 框架会在每次分发前注入当前架构上下文,因此派生类可以通过 Context 访问架构级服务。
|
||||
/// </summary>
|
||||
/// <typeparam name="TQuery">查询类型,必须实现IQuery接口</typeparam>
|
||||
/// <typeparam name="TResult">查询结果类型</typeparam>
|
||||
public abstract class AbstractQueryHandler<TQuery, TResult> : ContextAwareBase, IRequestHandler<TQuery, TResult>
|
||||
public abstract class AbstractQueryHandler<TQuery, TResult> : CqrsContextAwareHandlerBase,
|
||||
IRequestHandler<TQuery, TResult>
|
||||
where TQuery : IQuery<TResult>
|
||||
{
|
||||
/// <summary>
|
||||
@ -11,20 +11,19 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Query;
|
||||
namespace GFramework.Cqrs.Cqrs.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象流式查询处理器基类
|
||||
/// 继承自ContextAwareBase并实现IStreamQueryHandler接口,为具体的流式查询处理器提供基础功能
|
||||
/// 继承自轻量 CQRS 上下文基类并实现IStreamQueryHandler接口,为具体的流式查询处理器提供基础功能
|
||||
/// 支持流式处理查询并产生异步可枚举的响应序列,适用于大数据量或实时数据查询场景
|
||||
/// </summary>
|
||||
/// <typeparam name="TQuery">流式查询类型,必须实现IStreamQuery接口</typeparam>
|
||||
/// <typeparam name="TResponse">流式查询响应元素类型</typeparam>
|
||||
public abstract class AbstractStreamQueryHandler<TQuery, TResponse> : ContextAwareBase,
|
||||
public abstract class AbstractStreamQueryHandler<TQuery, TResponse> : CqrsContextAwareHandlerBase,
|
||||
IStreamRequestHandler<TQuery, TResponse>
|
||||
where TQuery : IStreamQuery<TResponse>
|
||||
{
|
||||
@ -11,17 +11,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Request;
|
||||
namespace GFramework.Cqrs.Cqrs.Request;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象请求处理器基类,用于处理不返回具体响应的请求
|
||||
/// 继承自ContextAwareBase并实现IRequestHandler接口
|
||||
/// 继承自轻量 CQRS 上下文基类并实现IRequestHandler接口
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">请求类型,必须实现IRequest[Unit]接口</typeparam>
|
||||
public abstract class AbstractRequestHandler<TRequest> : ContextAwareBase, IRequestHandler<TRequest, Unit>
|
||||
public abstract class AbstractRequestHandler<TRequest> : CqrsContextAwareHandlerBase, IRequestHandler<TRequest, Unit>
|
||||
where TRequest : IRequest<Unit>
|
||||
{
|
||||
/// <summary>
|
||||
@ -35,11 +34,11 @@ public abstract class AbstractRequestHandler<TRequest> : ContextAwareBase, IRequ
|
||||
|
||||
/// <summary>
|
||||
/// 抽象请求处理器基类,用于处理需要返回具体响应的请求
|
||||
/// 继承自ContextAwareBase并实现IRequestHandler接口
|
||||
/// 继承自轻量 CQRS 上下文基类并实现IRequestHandler接口
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">请求类型,必须实现IRequest[TResponse]接口</typeparam>
|
||||
/// <typeparam name="TResponse">响应类型</typeparam>
|
||||
public abstract class AbstractRequestHandler<TRequest, TResponse> : ContextAwareBase,
|
||||
public abstract class AbstractRequestHandler<TRequest, TResponse> : CqrsContextAwareHandlerBase,
|
||||
IRequestHandler<TRequest, TResponse> where TRequest : IRequest<TResponse>
|
||||
{
|
||||
/// <summary>
|
||||
@ -11,19 +11,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Request;
|
||||
namespace GFramework.Cqrs.Cqrs.Request;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象流式请求处理器基类
|
||||
/// 继承自ContextAwareBase并实现IStreamRequestHandler接口,为具体的流式请求处理器提供基础功能
|
||||
/// 继承自轻量 CQRS 上下文基类并实现IStreamRequestHandler接口,为具体的流式请求处理器提供基础功能
|
||||
/// 支持流式处理请求并产生异步可枚举的响应序列,适用于需要逐步返回结果的请求处理场景
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">流式请求类型,必须实现IStreamRequest接口</typeparam>
|
||||
/// <typeparam name="TResponse">流式请求响应元素类型</typeparam>
|
||||
public abstract class AbstractStreamRequestHandler<TRequest, TResponse> : ContextAwareBase,
|
||||
public abstract class AbstractStreamRequestHandler<TRequest, TResponse> : CqrsContextAwareHandlerBase,
|
||||
IStreamRequestHandler<TRequest, TResponse>
|
||||
where TRequest : IStreamRequest<TResponse>
|
||||
{
|
||||
@ -25,7 +25,7 @@ public static class CqrsTestRuntime
|
||||
|
||||
private static readonly Type CqrsHandlerRegistrarType = CqrsRuntimeAssembly
|
||||
.GetType(
|
||||
"GFramework.Core.Cqrs.Internal.CqrsHandlerRegistrar",
|
||||
"GFramework.Cqrs.Internal.CqrsHandlerRegistrar",
|
||||
throwOnError: true)!;
|
||||
|
||||
private static readonly MethodInfo RegisterHandlersMethod = CqrsHandlerRegistrarType
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user