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); } } }