using System.Collections.Concurrent;
using System.Diagnostics;
using Application.Abstractions.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Application.Messaging;
///
/// MediatR ISender/IMediator를 대체하는 사내 디스패처입니다.
/// 요청의 런타임 타입으로 Handler를 해석(리플렉션 + 캐싱)하여 호출하며,
/// 기존 LoggingBehavior의 처리 시간/느린 요청 로깅을 그대로 수행합니다.
///
internal sealed class Sender(IServiceProvider serviceProvider, ILogger logger) : IMediator
{
private const long SlowThresholdMs = 500;
private static readonly ConcurrentDictionary ResponseWrappers = new();
private static readonly ConcurrentDictionary VoidWrappers = new();
public async Task Send(IRequest request, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
var requestType = request.GetType();
var wrapper = (ResponseHandlerWrapper)ResponseWrappers.GetOrAdd(
requestType,
static (type, responseType) => ResponseHandlerWrapper.Create(type, responseType),
typeof(TResponse));
var requestName = requestType.Name;
var sw = Stopwatch.StartNew();
try
{
var result = await wrapper.Handle(serviceProvider, request, cancellationToken);
sw.Stop();
LogCompleted(requestName, sw.ElapsedMilliseconds);
return result;
}
catch (Exception ex)
{
sw.Stop();
logger.LogError(ex, "[Failed] {Request} after {Elapsed}ms", requestName, sw.ElapsedMilliseconds);
throw;
}
}
public async Task Send(IRequest request, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
var requestType = request.GetType();
var wrapper = VoidWrappers.GetOrAdd(requestType, static type => VoidHandlerWrapper.Create(type));
var requestName = requestType.Name;
var sw = Stopwatch.StartNew();
try
{
await wrapper.Handle(serviceProvider, request, cancellationToken);
sw.Stop();
LogCompleted(requestName, sw.ElapsedMilliseconds);
}
catch (Exception ex)
{
sw.Stop();
logger.LogError(ex, "[Failed] {Request} after {Elapsed}ms", requestName, sw.ElapsedMilliseconds);
throw;
}
}
private void LogCompleted(string requestName, long elapsedMs)
{
if (elapsedMs > SlowThresholdMs)
{
logger.LogWarning("[Slow] {Request} took {Elapsed}ms", requestName, elapsedMs);
}
else if (logger.IsEnabled(LogLevel.Debug))
{
logger.LogDebug("[Done] {Request} {Elapsed}ms", requestName, elapsedMs);
}
}
// 반환값이 있는 Command/Query 핸들러 호출 래퍼 (런타임 타입별 1회 생성 후 캐시)
private abstract class ResponseHandlerWrapper
{
public static ResponseHandlerWrapper Create(Type requestType, Type responseType)
{
var commandInterface = typeof(ICommand<>).MakeGenericType(responseType);
var queryInterface = typeof(IQuery<>).MakeGenericType(responseType);
var isCommand = commandInterface.IsAssignableFrom(requestType);
var isQuery = queryInterface.IsAssignableFrom(requestType);
// Handler 해석은 클래스 이름이 아니라 ICommand/IQuery 상속으로 결정됩니다.
// 요청은 ICommand 또는 IQuery 중 정확히 하나만 구현해야 하며,
// 그렇지 않으면 첫 디스패치 시 모호한 예외 대신 명확히 실패시킵니다.
if (isCommand == isQuery)
{
throw new InvalidOperationException(
$"요청 타입 '{requestType.Name}'은(는) ICommand<{responseType.Name}> 또는 IQuery<{responseType.Name}> 중 정확히 하나만 구현해야 합니다.");
}
var openWrapper = isCommand
? typeof(CommandResponseWrapper<,>)
: typeof(QueryResponseWrapper<,>);
var closedWrapper = openWrapper.MakeGenericType(requestType, responseType);
return (ResponseHandlerWrapper)Activator.CreateInstance(closedWrapper)!;
}
}
private abstract class ResponseHandlerWrapper : ResponseHandlerWrapper
{
public abstract Task Handle(IServiceProvider provider, object request, CancellationToken cancellationToken);
}
private sealed class CommandResponseWrapper : ResponseHandlerWrapper
where TCommand : ICommand
{
public override Task Handle(IServiceProvider provider, object request, CancellationToken cancellationToken)
{
var handler = provider.GetRequiredService>();
return handler.Handle((TCommand)request, cancellationToken);
}
}
private sealed class QueryResponseWrapper : ResponseHandlerWrapper
where TQuery : IQuery
{
public override Task Handle(IServiceProvider provider, object request, CancellationToken cancellationToken)
{
var handler = provider.GetRequiredService>();
return handler.Handle((TQuery)request, cancellationToken);
}
}
// 반환값이 없는 Command 핸들러 호출 래퍼
private abstract class VoidHandlerWrapper
{
public abstract Task Handle(IServiceProvider provider, object request, CancellationToken cancellationToken);
public static VoidHandlerWrapper Create(Type requestType)
{
var closedWrapper = typeof(VoidCommandWrapper<>).MakeGenericType(requestType);
return (VoidHandlerWrapper)Activator.CreateInstance(closedWrapper)!;
}
}
private sealed class VoidCommandWrapper : VoidHandlerWrapper
where TCommand : ICommand
{
public override Task Handle(IServiceProvider provider, object request, CancellationToken cancellationToken)
{
var handler = provider.GetRequiredService>();
return handler.Handle((TCommand)request, cancellationToken);
}
}
}