Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace GFramework.Core.Abstractions.Architectures;
/// <para>新的 <c>GFramework.Cqrs.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
public interface IArchitectureContext : ICqrsContext
{
/// <summary>
/// 获取指定类型的服务实例
Expand Down
54 changes: 9 additions & 45 deletions GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs
Original file line number Diff line number Diff line change
@@ -1,52 +1,16 @@
using GFramework.Core.Abstractions.Architectures;
using GFramework.Cqrs.Abstractions.Cqrs;
using System.ComponentModel;

namespace GFramework.Core.Abstractions.Cqrs;

/// <summary>
/// 定义架构上下文使用的 CQRS runtime seam。
/// 该抽象把请求分发、通知发布与流式处理从具体实现中解耦,
/// 使 <see cref="IArchitectureContext" /> 不再直接依赖某个固定的 runtime 类型。
/// 提供旧 <c>GFramework.Core.Abstractions.Cqrs</c> 命名空间下的 CQRS runtime 兼容别名。
/// </summary>
public interface ICqrsRuntime
/// <remarks>
/// 正式 runtime seam 已迁移到 <see cref="GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime" />,
/// 但当前仍保留该接口以避免立即打断历史公开路径与既有二进制引用。
/// 新代码应优先依赖 <c>GFramework.Cqrs.Abstractions.Cqrs</c> 下的正式契约。
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
public interface ICqrsRuntime : GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime
{
/// <summary>
/// 发送请求并返回响应。
/// </summary>
/// <typeparam name="TResponse">响应类型。</typeparam>
/// <param name="context">当前架构上下文,用于上下文感知处理器注入与嵌套请求访问。</param>
/// <param name="request">要分发的请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>请求响应。</returns>
ValueTask<TResponse> SendAsync<TResponse>(
IArchitectureContext context,
IRequest<TResponse> request,
CancellationToken cancellationToken = default);

/// <summary>
/// 发布通知到所有已注册处理器。
/// </summary>
/// <typeparam name="TNotification">通知类型。</typeparam>
/// <param name="context">当前架构上下文,用于上下文感知处理器注入。</param>
/// <param name="notification">要发布的通知。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>表示通知分发完成的值任务。</returns>
ValueTask PublishAsync<TNotification>(
IArchitectureContext context,
TNotification notification,
CancellationToken cancellationToken = default)
where TNotification : INotification;

/// <summary>
/// 创建流式请求的异步响应序列。
/// </summary>
/// <typeparam name="TResponse">流元素类型。</typeparam>
/// <param name="context">当前架构上下文,用于上下文感知处理器注入。</param>
/// <param name="request">流式请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>按需生成的异步响应序列。</returns>
IAsyncEnumerable<TResponse> CreateStream<TResponse>(
IArchitectureContext context,
IStreamRequest<TResponse> request,
CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
6 changes: 5 additions & 1 deletion GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -160,12 +160,16 @@ public void Get_Should_Return_First_Instance()
public void RegisterHandlers_Should_Not_Duplicate_Cqrs_Infrastructure_When_It_Is_Already_Registered()
{
Assert.That(_container.GetAll<ICqrsRuntime>(), Has.Count.EqualTo(1));
Assert.That(_container.GetAll<LegacyICqrsRuntime>(), Has.Count.EqualTo(1));
Assert.That(_container.GetAll<ICqrsHandlerRegistrar>(), Has.Count.EqualTo(1));
Assert.That(_container.Get<ICqrsRuntime>(), Is.SameAs(_container.Get<LegacyICqrsRuntime>()));

CqrsTestRuntime.RegisterHandlers(_container);

Assert.That(_container.GetAll<ICqrsRuntime>(), Has.Count.EqualTo(1));
Assert.That(_container.GetAll<LegacyICqrsRuntime>(), Has.Count.EqualTo(1));
Assert.That(_container.GetAll<ICqrsHandlerRegistrar>(), Has.Count.EqualTo(1));
Assert.That(_container.Get<ICqrsRuntime>(), Is.SameAs(_container.Get<LegacyICqrsRuntime>()));
}

/// <summary>
Expand Down
10 changes: 4 additions & 6 deletions GFramework.Core/Architectures/ArchitectureContext.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -190,7 +189,7 @@ public TResult SendQuery<TResult>(IQuery<TResult> query)
/// <typeparam name="TResponse">查询响应类型</typeparam>
/// <param name="query">要发送的查询对象</param>
/// <returns>查询结果</returns>
public TResponse SendQuery<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query)
public TResponse SendQuery<TResponse>(Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query)
{
return SendQueryAsync(query).AsTask().GetAwaiter().GetResult();
}
Expand All @@ -216,8 +215,7 @@ public async Task<TResult> SendQueryAsync<TResult>(IAsyncQuery<TResult> query)
/// <param name="query">要发送的查询对象</param>
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>包含查询结果的ValueTask</returns>
public async ValueTask<TResponse> SendQueryAsync<TResponse>(
GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query,
public async ValueTask<TResponse> SendQueryAsync<TResponse>(Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(query);
Expand Down Expand Up @@ -354,7 +352,7 @@ public IReadOnlyList<TUtility> GetUtilitiesByPriority<TUtility>() where TUtility
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>包含命令执行结果的ValueTask</returns>
public async ValueTask<TResponse> SendCommandAsync<TResponse>(
GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command,
Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(command);
Expand Down Expand Up @@ -393,7 +391,7 @@ public async Task<TResult> SendCommandAsync<TResult>(IAsyncCommand<TResult> comm
/// <typeparam name="TResponse">命令响应类型</typeparam>
/// <param name="command">要发送的命令对象</param>
/// <returns>命令执行结果</returns>
public TResponse SendCommand<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command)
public TResponse SendCommand<TResponse>(Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command)
{
return SendCommandAsync(command).AsTask().GetAwaiter().GetResult();
}
Expand Down
67 changes: 18 additions & 49 deletions GFramework.Core/Ioc/MicrosoftDiContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
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;
using GFramework.Cqrs.Abstractions.Cqrs;

namespace GFramework.Core.Ioc;
Expand Down Expand Up @@ -57,12 +57,6 @@ private void ThrowIfFrozen()
/// </summary>
private readonly HashSet<object> _registeredInstances = [];

/// <summary>
/// 已接入 CQRS handler 注册流程的程序集键集合。
/// 使用稳定字符串键而不是 Assembly 引用本身,以避免默认路径和显式扩展路径使用不同 Assembly 对象时重复注册。
/// </summary>
private readonly HashSet<string> _registeredCqrsHandlerAssemblyKeys = new(StringComparer.Ordinal);

/// <summary>
/// 日志记录器,用于记录容器操作日志
/// </summary>
Expand Down Expand Up @@ -406,26 +400,7 @@ public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
try
{
ThrowIfFrozen();

var processedAssemblyKeys = new HashSet<string>(StringComparer.Ordinal);
foreach (var assembly in assemblies
.Where(static assembly => assembly is not null)
.OrderBy(GetCqrsAssemblyRegistrationKey, StringComparer.Ordinal))
{
var assemblyKey = GetCqrsAssemblyRegistrationKey(assembly);
if (!processedAssemblyKeys.Add(assemblyKey))
continue;

if (_registeredCqrsHandlerAssemblyKeys.Contains(assemblyKey))
{
_logger.Debug(
$"Skipping CQRS handler registration for assembly {assemblyKey} because it was already registered.");
continue;
}

ResolveCqrsHandlerRegistrar().RegisterHandlers([assembly]);
_registeredCqrsHandlerAssemblyKeys.Add(assemblyKey);
}
ResolveCqrsRegistrationService().RegisterHandlers(assemblies);
}
finally
{
Expand Down Expand Up @@ -456,22 +431,22 @@ public void ExecuteServicesHook(Action<IServiceCollection>? configurator = null)
#region Get

/// <summary>
/// 获取当前容器中已注册的 CQRS 处理器注册器
/// 获取当前容器中已注册的 CQRS 程序集注册协调器
/// 该方法仅供容器内部在注册阶段使用,因此直接读取服务描述符中的实例绑定,
/// 避免在容器未冻结前依赖完整的服务提供者构建流程。
/// </summary>
/// <returns>已注册的 CQRS 处理器注册器实例。</returns>
/// <exception cref="InvalidOperationException">未找到可用的 CQRS 处理器注册器实例时抛出。</exception>
private ICqrsHandlerRegistrar ResolveCqrsHandlerRegistrar()
/// <returns>已注册的 CQRS 程序集注册协调器实例。</returns>
/// <exception cref="InvalidOperationException">未找到可用的 CQRS 程序集注册协调器实例时抛出。</exception>
private ICqrsRegistrationService ResolveCqrsRegistrationService()
{
var descriptor = GetServicesUnsafe.LastOrDefault(static service =>
service.ServiceType == typeof(ICqrsHandlerRegistrar));
service.ServiceType == typeof(ICqrsRegistrationService));

if (descriptor?.ImplementationInstance is ICqrsHandlerRegistrar registrar)
return registrar;
if (descriptor?.ImplementationInstance is ICqrsRegistrationService registrationService)
return registrationService;

const string errorMessage =
"ICqrsHandlerRegistrar not registered. Ensure the CQRS runtime module has been installed before registering handlers.";
"ICqrsRegistrationService not registered. Ensure the CQRS runtime module has been installed before registering handlers.";
_logger.Error(errorMessage);
throw new InvalidOperationException(errorMessage);
Comment on lines +454 to 465
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== 查找 ICqrsRegistrationService 的注册位置 =="
rg -n -C3 --type=cs '\bICqrsRegistrationService\b'

echo
echo "== 查找可能产生 ImplementationType / ImplementationFactory 的注册方式 =="
rg -n -C3 --type=cs \
  'RegisterSingleton<\s*ICqrsRegistrationService\s*,|RegisterFactory<\s*ICqrsRegistrationService\s*>\s*\(|AddSingleton<\s*ICqrsRegistrationService\s*,|AddSingleton\s*\(\s*typeof\s*\(\s*ICqrsRegistrationService\s*\)'

echo
echo "== 查找实例注册路径 =="
rg -n -C3 --type=cs \
  'CreateRegistrationService\s*\(|Register\s*\(\s*typeof\s*\(\s*ICqrsRegistrationService\s*\)|RegisterSingleton\s*\(\s*.*ICqrsRegistrationService'

Repository: GeWuYou/GFramework

Length of output: 5601


🏁 Script executed:

# 查看 MicrosoftDiContainer 类的 Register 方法签名
rg -n -A5 'public.*Register<' GFramework.Core/Ioc/MicrosoftDiContainer.cs | head -80

Repository: GeWuYou/GFramework

Length of output: 214


🏁 Script executed:

# 查看是否有 RegisterSingleton 或 RegisterFactory 的公开方法
rg -n 'public.*Register(Singleton|Factory)' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 217


🏁 Script executed:

# 查看整个 MicrosoftDiContainer 类以了解其 API
wc -l GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 109


🏁 Script executed:

# 查看 RegisterSingleton<TService, TImpl> 的实现
sed -n '116,130p' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 493


🏁 Script executed:

# 查看 RegisterFactory<TService> 的实现
sed -n '305,325p' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 549


🏁 Script executed:

# 查看容器如何解析服务(Get/GetRequired 方法)
rg -n -A10 'public.*Get<TService>\(|public.*GetRequired<TService>\(' GFramework.Core/Ioc/MicrosoftDiContainer.cs | head -50

Repository: GeWuYou/GFramework

Length of output: 44


🏁 Script executed:

# 查看 Get 和 GetRequired 方法
rg -n -B2 -A15 'public.*TService\?.*Get<TService>\(|public.*GetRequired<TService>\(' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 44


🏁 Script executed:

# 查找 ResolveCqrsRegistrationService 的调用点
rg -n 'ResolveCqrsRegistrationService' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 217


🏁 Script executed:

# 查看 ResolveCqrsRegistrationService 被调用的上下文
rg -n -B5 -A2 'ResolveCqrsRegistrationService\(\)' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 711


🏁 Script executed:

# 查看 Get/GetRequired 完整实现,使用 sed 查看具体行号
sed -n '350,400p' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 2050


🏁 Script executed:

# 查看注册 ICqrsRegistrationService 的所有位置和方式
rg -n -B3 -A3 'Register.*ICqrsRegistrationService' GFramework.Core/

Repository: GeWuYou/GFramework

Length of output: 787


🏁 Script executed:

# 检查是否有其他使用 RegisterSingleton<T, TImpl> 的例子来理解是否允许这样做
rg -n 'RegisterSingleton<.*,.*>' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 118


🏁 Script executed:

# 查看 RegisterCqrsHandlersFromAssemblies 的上下文和完整注释
sed -n '408,425p' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 454


🏁 Script executed:

# 查看注释中关于"实例绑定"的部分
sed -n '445,455p' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 455


🏁 Script executed:

# 确认一下 Get 方法的完整实现,查看它如何处理不同类型的描述符
rg -n -A30 'public.*\?.*Get<TService>' GFramework.Core/Ioc/MicrosoftDiContainer.cs | head -60

Repository: GeWuYou/GFramework

Length of output: 44


🏁 Script executed:

# 查看容器的公开 Register 方法的 XML 文档注释
sed -n '255,275p' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 678


🏁 Script executed:

# 查看 RegisterSingleton<TService, TImpl> 的 XML 文档注释
sed -n '105,120p' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 483


🏁 Script executed:

# 查看 RegisterFactory 的 XML 文档注释
sed -n '295,315p' GFramework.Core/Ioc/MicrosoftDiContainer.cs

Repository: GeWuYou/GFramework

Length of output: 621


🏁 Script executed:

# 查看 CqrsRuntimeModule 的完整上下文,看它如何安装
sed -n '30,55p' GFramework.Core/Services/Modules/CqrsRuntimeModule.cs

Repository: GeWuYou/GFramework

Length of output: 1210


ResolveCqrsRegistrationService() 只检查实例绑定,但容器公开 API 允许其他注册方式。

容器支持 RegisterSingleton<TService, TImpl>()RegisterFactory<TService>() 作为公开 API,但此方法仅检查 descriptor?.ImplementationInstance。如果用户通过这些 API 注册 ICqrsRegistrationService(即使是合法的用法),也会触发"not registered"错误。

虽然当前官方模块(CqrsRuntimeModule)只用实例方式注册,注释也说明了这是有意的设计(避免在容器未冻结时完整构建),但这个约束应该更显式地防守或文档化。建议:

  1. 在方法中添加 null 检查和更具体的错误消息,说明只支持实例注册
  2. 或在 CqrsRuntimeModule 和相关文档中明确标注 ICqrsRegistrationService 只能通过实例注册
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@GFramework.Core/Ioc/MicrosoftDiContainer.cs` around lines 454 - 465,
ResolveCqrsRegistrationService currently only accepts a descriptor with an
ImplementationInstance and treats any other registration as "not registered";
update the method so if a descriptor exists but
descriptor.ImplementationInstance is null it throws a more specific
InvalidOperationException (and logs) that states ICqrsRegistrationService must
be registered as an instance (e.g. via CqrsRuntimeModule or RegisterSingleton
with an instance) and that other registration forms (factory/type) are
unsupported, and keep the original "not registered" message only when no
descriptor is found; reference ResolveCqrsRegistrationService,
ICqrsRegistrationService, GetServicesUnsafe and CqrsRuntimeModule in the message
to guide callers.

}
Expand Down Expand Up @@ -624,11 +599,14 @@ public IReadOnlyList<T> GetAll<T>() where T : class
.Where(s => s.ServiceType == serviceType || serviceType.IsAssignableFrom(s.ServiceType)).ToList();

var result = new List<T>();
var seenInstances = new HashSet<object>(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)
{
Expand Down Expand Up @@ -672,11 +650,14 @@ public IReadOnlyList<object> GetAll(Type type)
.ToList();

var result = new List<object>();
var seenInstances = new HashSet<object>(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)
{
Expand Down Expand Up @@ -827,7 +808,6 @@ public void Clear()

GetServicesUnsafe.Clear();
_registeredInstances.Clear();
_registeredCqrsHandlerAssemblyKeys.Clear();
_provider = null;
_logger.Info("Container cleared");
}
Expand Down Expand Up @@ -899,16 +879,5 @@ public IServiceScope CreateScope()
}
}

/// <summary>
/// 生成 CQRS handler 注册用的稳定程序集键。
/// 该键需要同时兼顾真实程序集与测试中使用的 mocked Assembly,避免仅靠引用比较导致重复接入。
/// </summary>
/// <param name="assembly">目标程序集。</param>
/// <returns>稳定的程序集标识字符串。</returns>
private static string GetCqrsAssemblyRegistrationKey(Assembly assembly)
{
return assembly.FullName ?? assembly.GetName().Name ?? assembly.ToString();
}

#endregion
}
13 changes: 9 additions & 4 deletions GFramework.Core/Services/Modules/CqrsRuntimeModule.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -39,10 +39,15 @@ public void Register(IIocContainer container)

var dispatcherLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher");
var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar");
var registrationLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsRegistrationService");
var runtime = CqrsRuntimeFactory.CreateRuntime(container, dispatcherLogger);
var registrar = CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger);

container.Register<ICqrsRuntime>(CqrsRuntimeFactory.CreateRuntime(container, dispatcherLogger));
container.Register<ICqrsHandlerRegistrar>(
CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger));
container.Register(runtime);
container.Register<LegacyICqrsRuntime>((LegacyICqrsRuntime)runtime);
container.Register<ICqrsHandlerRegistrar>(registrar);
container.Register<ICqrsRegistrationService>(
CqrsRuntimeFactory.CreateRegistrationService(registrar, registrationLogger));
}

/// <summary>
Expand Down
13 changes: 13 additions & 0 deletions GFramework.Cqrs.Abstractions/Cqrs/ICqrsContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace GFramework.Cqrs.Abstractions.Cqrs;

/// <summary>
/// 定义 CQRS runtime 在分发期间携带的最小上下文标记。
/// </summary>
/// <remarks>
/// 该接口当前刻意保持为轻量 marker seam,只用于让 <see cref="ICqrsRuntime" /> 从
/// <c>GFramework.Core.Abstractions</c> 的 <c>IArchitectureContext</c> 解耦。
/// 运行时实现仍可在需要时识别更具体的上下文类型,并对现有 <c>IContextAware</c> 处理器执行兼容注入。
/// </remarks>
public interface ICqrsContext
{
}
49 changes: 49 additions & 0 deletions GFramework.Cqrs.Abstractions/Cqrs/ICqrsRuntime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace GFramework.Cqrs.Abstractions.Cqrs;

/// <summary>
/// 定义架构上下文使用的 CQRS runtime seam。
/// 该抽象把请求分发、通知发布与流式处理从具体实现中解耦,
/// 使 CQRS runtime 契约可独立归属到 <c>GFramework.Cqrs.Abstractions</c>。
/// </summary>
public interface ICqrsRuntime
{
/// <summary>
/// 发送请求并返回响应。
/// </summary>
/// <typeparam name="TResponse">响应类型。</typeparam>
/// <param name="context">当前 CQRS 分发上下文。</param>
/// <param name="request">要分发的请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>请求响应。</returns>
ValueTask<TResponse> SendAsync<TResponse>(
ICqrsContext context,
IRequest<TResponse> request,
CancellationToken cancellationToken = default);

/// <summary>
/// 发布通知到所有已注册处理器。
/// </summary>
/// <typeparam name="TNotification">通知类型。</typeparam>
/// <param name="context">当前 CQRS 分发上下文。</param>
/// <param name="notification">要发布的通知。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>表示通知分发完成的值任务。</returns>
ValueTask PublishAsync<TNotification>(
ICqrsContext context,
TNotification notification,
CancellationToken cancellationToken = default)
where TNotification : INotification;

/// <summary>
/// 创建流式请求的异步响应序列。
/// </summary>
/// <typeparam name="TResponse">流元素类型。</typeparam>
/// <param name="context">当前 CQRS 分发上下文。</param>
/// <param name="request">流式请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>按需生成的异步响应序列。</returns>
IAsyncEnumerable<TResponse> CreateStream<TResponse>(
ICqrsContext context,
IStreamRequest<TResponse> request,
CancellationToken cancellationToken = default);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Loading
Loading