diff --git a/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs b/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs index c7b62e2f..2e8894bc 100644 --- a/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs +++ b/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs @@ -18,7 +18,7 @@ namespace GFramework.Core.Abstractions.Architectures; /// 新的 GFramework.Cqrs.Abstractions.Cqrs 契约由内置 CQRS dispatcher 统一处理,支持 request pipeline、notification publish 与 stream request。 /// 新功能优先使用 与对应的 CQRS Command/Query 重载;迁移旧代码时可先保留旧入口,再逐步替换为 CQRS 请求模型。 /// -public interface IArchitectureContext +public interface IArchitectureContext : ICqrsContext { /// /// 获取指定类型的服务实例 diff --git a/GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs b/GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs index d9efcb07..5bcbf862 100644 --- a/GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs +++ b/GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs @@ -1,52 +1,16 @@ -using GFramework.Core.Abstractions.Architectures; -using GFramework.Cqrs.Abstractions.Cqrs; +using System.ComponentModel; namespace GFramework.Core.Abstractions.Cqrs; /// -/// 定义架构上下文使用的 CQRS runtime seam。 -/// 该抽象把请求分发、通知发布与流式处理从具体实现中解耦, -/// 使 不再直接依赖某个固定的 runtime 类型。 +/// 提供旧 GFramework.Core.Abstractions.Cqrs 命名空间下的 CQRS runtime 兼容别名。 /// -public interface ICqrsRuntime +/// +/// 正式 runtime seam 已迁移到 , +/// 但当前仍保留该接口以避免立即打断历史公开路径与既有二进制引用。 +/// 新代码应优先依赖 GFramework.Cqrs.Abstractions.Cqrs 下的正式契约。 +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public interface ICqrsRuntime : GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime { - /// - /// 发送请求并返回响应。 - /// - /// 响应类型。 - /// 当前架构上下文,用于上下文感知处理器注入与嵌套请求访问。 - /// 要分发的请求。 - /// 取消令牌。 - /// 请求响应。 - ValueTask SendAsync( - IArchitectureContext context, - IRequest request, - CancellationToken cancellationToken = default); - - /// - /// 发布通知到所有已注册处理器。 - /// - /// 通知类型。 - /// 当前架构上下文,用于上下文感知处理器注入。 - /// 要发布的通知。 - /// 取消令牌。 - /// 表示通知分发完成的值任务。 - ValueTask PublishAsync( - IArchitectureContext context, - TNotification notification, - CancellationToken cancellationToken = default) - where TNotification : INotification; - - /// - /// 创建流式请求的异步响应序列。 - /// - /// 流元素类型。 - /// 当前架构上下文,用于上下文感知处理器注入。 - /// 流式请求。 - /// 取消令牌。 - /// 按需生成的异步响应序列。 - IAsyncEnumerable CreateStream( - IArchitectureContext context, - IStreamRequest request, - CancellationToken cancellationToken = default); } diff --git a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs index cdcde44d..584090ab 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs @@ -1,7 +1,6 @@ using System.Reflection; using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Command; -using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Enums; using GFramework.Core.Abstractions.Environment; using GFramework.Core.Abstractions.Ioc; diff --git a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs index 4104ccb1..39445d48 100644 --- a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs +++ b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs @@ -1,12 +1,12 @@ using System.Reflection; using GFramework.Core.Abstractions.Bases; -using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Ioc; using GFramework.Core.Logging; using GFramework.Core.Tests.Cqrs; using GFramework.Core.Tests.Systems; using GFramework.Cqrs.Abstractions.Cqrs; +using LegacyICqrsRuntime = GFramework.Core.Abstractions.Cqrs.ICqrsRuntime; namespace GFramework.Core.Tests.Ioc; @@ -160,12 +160,16 @@ public class MicrosoftDiContainerTests public void RegisterHandlers_Should_Not_Duplicate_Cqrs_Infrastructure_When_It_Is_Already_Registered() { Assert.That(_container.GetAll(), Has.Count.EqualTo(1)); + Assert.That(_container.GetAll(), Has.Count.EqualTo(1)); Assert.That(_container.GetAll(), Has.Count.EqualTo(1)); + Assert.That(_container.Get(), Is.SameAs(_container.Get())); CqrsTestRuntime.RegisterHandlers(_container); Assert.That(_container.GetAll(), Has.Count.EqualTo(1)); + Assert.That(_container.GetAll(), Has.Count.EqualTo(1)); Assert.That(_container.GetAll(), Has.Count.EqualTo(1)); + Assert.That(_container.Get(), Is.SameAs(_container.Get())); } /// diff --git a/GFramework.Core/Architectures/ArchitectureContext.cs b/GFramework.Core/Architectures/ArchitectureContext.cs index 9b6d7dc2..e0ac2dd6 100644 --- a/GFramework.Core/Architectures/ArchitectureContext.cs +++ b/GFramework.Core/Architectures/ArchitectureContext.cs @@ -1,7 +1,6 @@ using System.Collections.Concurrent; using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Command; -using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Environment; using GFramework.Core.Abstractions.Events; using GFramework.Core.Abstractions.Ioc; @@ -190,7 +189,7 @@ public class ArchitectureContext : IArchitectureContext /// 查询响应类型 /// 要发送的查询对象 /// 查询结果 - public TResponse SendQuery(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query) + public TResponse SendQuery(Cqrs.Abstractions.Cqrs.Query.IQuery query) { return SendQueryAsync(query).AsTask().GetAwaiter().GetResult(); } @@ -216,8 +215,7 @@ public class ArchitectureContext : IArchitectureContext /// 要发送的查询对象 /// 取消令牌,用于取消操作 /// 包含查询结果的ValueTask - public async ValueTask SendQueryAsync( - GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query, + public async ValueTask SendQueryAsync(Cqrs.Abstractions.Cqrs.Query.IQuery query, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(query); @@ -354,7 +352,7 @@ public class ArchitectureContext : IArchitectureContext /// 取消令牌,用于取消操作 /// 包含命令执行结果的ValueTask public async ValueTask SendCommandAsync( - GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command, + Cqrs.Abstractions.Cqrs.Command.ICommand command, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(command); @@ -393,7 +391,7 @@ public class ArchitectureContext : IArchitectureContext /// 命令响应类型 /// 要发送的命令对象 /// 命令执行结果 - public TResponse SendCommand(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command) + public TResponse SendCommand(Cqrs.Abstractions.Cqrs.Command.ICommand command) { return SendCommandAsync(command).AsTask().GetAwaiter().GetResult(); } diff --git a/GFramework.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs index dc14485e..390f4c91 100644 --- a/GFramework.Core/Ioc/MicrosoftDiContainer.cs +++ b/GFramework.Core/Ioc/MicrosoftDiContainer.cs @@ -4,7 +4,6 @@ using GFramework.Core.Abstractions.Bases; using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Systems; -using GFramework.Core.Logging; using GFramework.Core.Rule; using GFramework.Cqrs.Abstractions.Cqrs; @@ -624,11 +623,14 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) .Where(s => s.ServiceType == serviceType || serviceType.IsAssignableFrom(s.ServiceType)).ToList(); var result = new List(); + var seenInstances = new HashSet(ReferenceEqualityComparer.Instance); foreach (var descriptor in registeredServices) { if (descriptor.ImplementationInstance is T instance) { - result.Add(instance); + // 同一实例可能同时以“正式接口 + 兼容别名接口”被注册;未冻结路径需去重以保持与冻结后的解析口径一致。 + if (seenInstances.Add(instance)) + result.Add(instance); } else if (descriptor.ImplementationFactory != null) { @@ -672,11 +674,14 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) .ToList(); var result = new List(); + var seenInstances = new HashSet(ReferenceEqualityComparer.Instance); foreach (var descriptor in registeredServices) { if (descriptor.ImplementationInstance != null) { - result.Add(descriptor.ImplementationInstance); + // 同一实例可能通过多个可赋值服务类型暴露;返回前按引用去重,避免兼容别名造成重复观察结果。 + if (seenInstances.Add(descriptor.ImplementationInstance)) + result.Add(descriptor.ImplementationInstance); } else if (descriptor.ImplementationFactory != null) { diff --git a/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs b/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs index 3fe37558..9a36d003 100644 --- a/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs +++ b/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs @@ -1,9 +1,9 @@ using GFramework.Core.Abstractions.Architectures; -using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Logging; using GFramework.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs; +using LegacyICqrsRuntime = GFramework.Core.Abstractions.Cqrs.ICqrsRuntime; namespace GFramework.Core.Services.Modules; @@ -39,8 +39,10 @@ public sealed class CqrsRuntimeModule : IServiceModule var dispatcherLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher"); var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar"); + var runtime = CqrsRuntimeFactory.CreateRuntime(container, dispatcherLogger); - container.Register(CqrsRuntimeFactory.CreateRuntime(container, dispatcherLogger)); + container.Register(runtime); + container.Register((LegacyICqrsRuntime)runtime); container.Register( CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger)); } diff --git a/GFramework.Cqrs.Abstractions/Cqrs/ICqrsContext.cs b/GFramework.Cqrs.Abstractions/Cqrs/ICqrsContext.cs new file mode 100644 index 00000000..09fc355e --- /dev/null +++ b/GFramework.Cqrs.Abstractions/Cqrs/ICqrsContext.cs @@ -0,0 +1,13 @@ +namespace GFramework.Cqrs.Abstractions.Cqrs; + +/// +/// 定义 CQRS runtime 在分发期间携带的最小上下文标记。 +/// +/// +/// 该接口当前刻意保持为轻量 marker seam,只用于让 从 +/// GFramework.Core.AbstractionsIArchitectureContext 解耦。 +/// 运行时实现仍可在需要时识别更具体的上下文类型,并对现有 IContextAware 处理器执行兼容注入。 +/// +public interface ICqrsContext +{ +} diff --git a/GFramework.Cqrs.Abstractions/Cqrs/ICqrsRuntime.cs b/GFramework.Cqrs.Abstractions/Cqrs/ICqrsRuntime.cs new file mode 100644 index 00000000..b8c85124 --- /dev/null +++ b/GFramework.Cqrs.Abstractions/Cqrs/ICqrsRuntime.cs @@ -0,0 +1,49 @@ +namespace GFramework.Cqrs.Abstractions.Cqrs; + +/// +/// 定义架构上下文使用的 CQRS runtime seam。 +/// 该抽象把请求分发、通知发布与流式处理从具体实现中解耦, +/// 使 CQRS runtime 契约可独立归属到 GFramework.Cqrs.Abstractions。 +/// +public interface ICqrsRuntime +{ + /// + /// 发送请求并返回响应。 + /// + /// 响应类型。 + /// 当前 CQRS 分发上下文。 + /// 要分发的请求。 + /// 取消令牌。 + /// 请求响应。 + ValueTask SendAsync( + ICqrsContext context, + IRequest request, + CancellationToken cancellationToken = default); + + /// + /// 发布通知到所有已注册处理器。 + /// + /// 通知类型。 + /// 当前 CQRS 分发上下文。 + /// 要发布的通知。 + /// 取消令牌。 + /// 表示通知分发完成的值任务。 + ValueTask PublishAsync( + ICqrsContext context, + TNotification notification, + CancellationToken cancellationToken = default) + where TNotification : INotification; + + /// + /// 创建流式请求的异步响应序列。 + /// + /// 流元素类型。 + /// 当前 CQRS 分发上下文。 + /// 流式请求。 + /// 取消令牌。 + /// 按需生成的异步响应序列。 + IAsyncEnumerable CreateStream( + ICqrsContext context, + IStreamRequest request, + CancellationToken cancellationToken = default); +} diff --git a/GFramework.Cqrs/CqrsRuntimeFactory.cs b/GFramework.Cqrs/CqrsRuntimeFactory.cs index 0a0f86ce..1357975d 100644 --- a/GFramework.Cqrs/CqrsRuntimeFactory.cs +++ b/GFramework.Cqrs/CqrsRuntimeFactory.cs @@ -1,4 +1,3 @@ -using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Logging; using GFramework.Cqrs.Abstractions.Cqrs; diff --git a/GFramework.Cqrs/Internal/CqrsDispatcher.cs b/GFramework.Cqrs/Internal/CqrsDispatcher.cs index dafea402..9a125789 100644 --- a/GFramework.Cqrs/Internal/CqrsDispatcher.cs +++ b/GFramework.Cqrs/Internal/CqrsDispatcher.cs @@ -1,17 +1,17 @@ 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; using GFramework.Cqrs.Abstractions.Cqrs; +using ICqrsRuntime = GFramework.Core.Abstractions.Cqrs.ICqrsRuntime; namespace GFramework.Cqrs.Internal; /// /// GFramework 自有 CQRS 运行时分发器。 -/// 该类型负责解析请求/通知处理器,并在调用前为上下文感知对象注入当前架构上下文。 +/// 该类型负责解析请求/通知处理器,并在调用前为上下文感知对象注入当前 CQRS 分发上下文。 /// internal sealed class CqrsDispatcher( IIocContainer container, @@ -38,11 +38,11 @@ internal sealed class CqrsDispatcher( /// 发布通知到所有已注册处理器。 /// /// 通知类型。 - /// 当前架构上下文,用于上下文感知处理器注入。 + /// 当前 CQRS 分发上下文,用于上下文感知处理器注入。 /// 通知对象。 /// 取消令牌。 public async ValueTask PublishAsync( - IArchitectureContext context, + ICqrsContext context, TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification @@ -75,12 +75,12 @@ internal sealed class CqrsDispatcher( /// 发送请求并返回结果。 /// /// 响应类型。 - /// 当前架构上下文,用于上下文感知处理器注入。 + /// 当前 CQRS 分发上下文,用于上下文感知处理器注入。 /// 请求对象。 /// 取消令牌。 /// 请求响应。 public async ValueTask SendAsync( - IArchitectureContext context, + ICqrsContext context, IRequest request, CancellationToken cancellationToken = default) { @@ -122,12 +122,12 @@ internal sealed class CqrsDispatcher( /// 创建流式请求并返回异步响应序列。 /// /// 响应元素类型。 - /// 当前架构上下文,用于上下文感知处理器注入。 + /// 当前 CQRS 分发上下文,用于上下文感知处理器注入。 /// 流式请求对象。 /// 取消令牌。 /// 异步响应序列。 public IAsyncEnumerable CreateStream( - IArchitectureContext context, + ICqrsContext context, IStreamRequest request, CancellationToken cancellationToken = default) { @@ -150,14 +150,20 @@ internal sealed class CqrsDispatcher( } /// - /// 为上下文感知处理器注入当前架构上下文。 + /// 为上下文感知处理器注入当前 CQRS 分发上下文。 /// /// 处理器实例。 - /// 当前架构上下文。 - private static void PrepareHandler(object handler, IArchitectureContext context) + /// 当前 CQRS 分发上下文。 + private static void PrepareHandler(object handler, ICqrsContext context) { if (handler is IContextAware contextAware) - contextAware.SetContext(context); + { + if (context is not IArchitectureContext architectureContext) + throw new InvalidOperationException( + "The current CQRS context does not implement IArchitectureContext, so it cannot be injected into IContextAware handlers."); + + contextAware.SetContext(architectureContext); + } } /// diff --git a/GFramework.Tests.Common/CqrsTestRuntime.cs b/GFramework.Tests.Common/CqrsTestRuntime.cs index e7c971db..7acbbe35 100644 --- a/GFramework.Tests.Common/CqrsTestRuntime.cs +++ b/GFramework.Tests.Common/CqrsTestRuntime.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; using System.Reflection; -using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Ioc; using GFramework.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Command; +using LegacyICqrsRuntime = GFramework.Core.Abstractions.Cqrs.ICqrsRuntime; namespace GFramework.Tests.Common; @@ -60,7 +60,12 @@ public static class CqrsTestRuntime { var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher"); var runtime = CqrsRuntimeFactory.CreateRuntime(container, runtimeLogger); - container.Register(runtime); + container.Register(runtime); + container.Register((LegacyICqrsRuntime)runtime); + } + else if (container.Get() is null) + { + container.Register((LegacyICqrsRuntime)container.GetRequired()); } if (container.Get() is null)