MediatR로 도메인 이벤트 게시

처리기 내에서 비즈니스 논리를 구현할 때 처리의 부작용으로 일부 작업을 수행할 수 있습니다.

여러 핸들러에서 일부를 수행하려는 경우 핸들러에 코드가 있으면 코드가 부풀어 오르거나 중복될 수 있습니다.

MediatR 알림은 이러한 경우에 유용할 수 있습니다. 방법을 살펴보겠습니다!

설정



먼저 예제 설정을 생성해 보겠습니다. .NET 6을 사용하여 간단한 웹 API를 생성합니다.

~$ dotnet new webapi -o MediatrNotification


즐겨찾는 편집기에서 초기 상용구( WeatherForecast.csControllers/WheatherForecastController.cs ) 항목을 제거합니다.

마지막으로 MediatR을 추가하고 초기화합니다.

~$ dotnet add package MediatR
~$ dotnet add package MediatR.Extensions.Microsoft.DependencyInjection



// Program.cs
+ using MediatR;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
+ builder.Services.AddMediatR(typeof(Program));


사용 사례



데모의 경우 누군가 꽃다발을 주문할 수 있는 단일 엔드포인트가 있습니다.

해당 예제를 기반으로 새 파일PlaceBouquetOrderRequest.cs에서 요청을 생성할 수 있습니다.

// PlaceBouquetOrderRequest.cs
public class PlaceBouquetOrderRequest : IRequest<Guid>
{
    public DateTime DueDate { get; init; }
    public int FlowersCount { get; init; }
    public string? Note { get; init; }
}


그 옆에 핸들러를 초기화합니다.

// PlaceBouquetOrderRequest.cs
public class PlaceBouquetOrderRequestHandler : IRequestHandler<PlaceBouquetOrderRequest, Guid>
{
    public Task<Guid> Handle(PlaceBouquetOrderRequest request, CancellationToken cancellationToken)
    {
        var orderId = new Guid();

        // Send the order to the merchant

        return Task.FromResult(orderId);
    }
}


마지막으로 Controllers/BouquetController.cs에 연결된 컨트롤러를 만듭니다.

// BouquetController.cs
[Route("api/[controller]")]
public class BouquetController : ControllerBase
{
    private readonly IMediator _mediator;

    public BouquetController(IMediator mediator)
        => _mediator = mediator;

    [HttpPost("order")]
    public async Task<IActionResult> PlaceBouquetOrder([FromBody] PlaceBouquetOrderRequest request)
    {
        var orderId = await _mediator.Send(request);
        return Ok(orderId);
    }
}


부작용 추가



우리의 앱은 훌륭하게 실행되고 있지만 이제 고객은 일정에 대한 개요를 볼 수 있도록 판매자의 캘린더에도 이벤트를 보내길 원합니다.

Please note that we won't perform any validation here since that's not the goal of this tutorial


PlaceBouquetOrderRequest.cs로 돌아가서 추가 변경 사항을 추가해 보겠습니다.

public class PlaceBouquetOrderRequestHandler : IRequestHandler<PlaceBouquetOrderRequest, Guid>
{
    public Task<Guid> Handle(PlaceBouquetOrderRequest request, CancellationToken cancellationToken)
    {
        var orderId = new Guid();

        // Send the order to the merchant
       SendReminderToCalendarAt(request.DueDate);

        return Task.FromResult(orderId);
    }

   private void SendReminderToCalendarAt(DateTime dueDate)
   {
       // Send a reminder to the merchant's calendar
   }
}


문제



불행히도 거기에서 찾을 수 있는 몇 가지 문제가 있습니다.
  • 한때 꽃다발 주문을 담당했던 우리PlaceBouquetOrderRequestHandler는 이제 일정 미리 알림도 담당합니다. 그 범위가 원래 책임 범위를 벗어나고 있습니다
  • .
  • SendReminder 논리는 다른 곳에서 재사용할 수 있으며 메서드를 복제하거나 전용 서비스로 추출해야 합니다. 그러나 서비스를 만들면 객체가 핸들러를 중심으로 설계된 코드 구조를 변경하게 될 수 있습니다.

  • 해결책



    잠시 생각해 보면 요청된 작업은 단순히 알림을 보내는 것보다 "주문이 완료되면 작업을 수행하는 것"에 관한 것입니다.

    다행스럽게도 MediatR에는 이러한 이벤트를 나타내고 처리하는 개체가 있으며 이를 알림이라고 합니다.

    우리의 사건을 해결하기 위해 하나를 만들어 봅시다!

    BouquetOrderPlacedEvent.cs에서 다음 이벤트를 생성합니다.

    // BouquetOrderPlacedEvent.cs
    public class BouquetOrderPlacedEvent : INotification
    {
        public Guid OrderId { get; init; }
        public DateTime DueDate { get; init; }
    }
    


    이제 이러한 종류의 알림을 처리할 수 있는 이벤트 핸들러를 만들 수 있습니다.

    // BouquetOrderPlacedEvent.cs
    public class BouquetOrderPlacedEventHandler : INotificationHandler<BouquetOrderPlacedEvent>
    {
        public Task Handle(BouquetOrderPlacedEvent notification, CancellationToken cancellationToken)
        {
            SendReminderToCalendarAt(notification.DueDate);
    
            return Task.CompletedTask;
        }
    
        private void SendReminderToCalendarAt(DateTime dueDate)
        {
            // Send a reminder to the merchant's calendar
        }
    }
    


    그리고 핸들러의 이전 논리를 이 이벤트의 방출로 대체합니다.

    // PlaceBouquetOrderRequestHandler.cs
    public class PlaceBouquetOrderRequestHandler : IRequestHandler<PlaceBouquetOrderRequest, Guid>
    {
        private readonly IPublisher _publisher;
    
        public PlaceBouquetOrderRequestHandler(IPublisher publisher)
            => _publisher = publisher;
    
        public Task<Guid> Handle(PlaceBouquetOrderRequest request, CancellationToken cancellationToken)
        {
            var orderId = new Guid();
    
            // Send the order to the merchant
    
             _publisher.Publish(new BouquetOrderPlacedEvent
            {
                OrderId = orderId,
                DueDate = request.DueDate
            });
    
            return Task.FromResult(orderId);
        }
    }
    


    더 나아가



    새로운 종류의 주문을 처리할 계획이라면 이벤트를 OrderPlacedEvent로 일반화하여 주문 종류에서 추상화할 수 있습니다.

    // BouquetOrderPlacedEvent.cs
    public abstract class OrderPlacedEvent : INotification
    {
        public Guid OrderId { get; init; }
        public DateTime DueDate { get; init; }
    }
    
    public class BouquetOrderPlacedEvent : OrderPlacedEvent { }
    


    그런 다음 기본 클래스에서 파생된 모든 이벤트를 처리할 수 있도록 처리기를 일반화할 수 있습니다OrderPlacedEvent.

    public class OrderPlacedEventHandler<TOrderPlacedEvent> : INotificationHandler<TOrderPlacedEvent>
        where TOrderPlacedEvent : OrderPlacedEvent
    {
        public Task Handle(TOrderPlacedEvent notification, CancellationToken cancellationToken)
        {
            SendReminderToCalendarAt(notification.DueDate);
    
            return Task.CompletedTask;
        }
    
        private void SendReminderToCalendarAt(DateTime dueDate)
        {
            // Send a reminder to the merchant's calendar
        }
    }
    


    Note that if we had just changed our handler's definition to public class OrderPlacedEventHandler : INotificationHandler<OrderPlacedEvent>, MediatR would not have correctly route the event to our handler. You can read more about it on this issue.



    테이크 아웃



    짜잔, 우리는 로직을 전용 핸들러로 옮겼고 나중에 다른 유형의 순서도 처리할 수 있습니다. 또한 처리기의 논리를 현재 사용 사례에 최대한 가깝게 유지했으며 타사 캘린더 서비스와의 통신을 실제로 구현했다면 처리기의 종속성을 줄였을 것입니다.

    좋은 웹페이지 즐겨찾기