Contents

CQRS與MediatR(三)

CQRS與MediatR(三)

MediatR簡介

在 NET 中說到 CQRS,常伴隨著 MediatR,如前篇所示,其實 CQRS 與 MediatR 並沒有絕對的掛鉤,即使不使用 MediatR 也可以實作 CQRS,那麼 MediatR 魅力到底是什麼?,為什麼多數情況下,CQRS 的專案,通常會使用 MediatR 套件呢?

MediatR 的作者是 Jimmy Bogard (同時也是 AutoMapper 作者),是一種中介者模式實現

中介者模式定義了一個中介者對象,該對象封裝了系統中對象間的交互方式,對象間的通信過程被封裝在一個中介者(調解人)對象之中。 對象之間不再直接交互,而是通過調解人進行交互。 這麼做可以減少可交互對象間的依賴,從而降低耦合。(Wiki)

以現實角度解釋,就是類似仲介般的角色,如自己找房子與委託房仲的差異。自行買賣房屋,買家要面的是無數的賣家,而賣家也要面對無數的買家,帶屋看房這些繁雜的事物,均要親力親為;委託仲介可以將許多複雜的交易情況簡化,我們不需要自己去找房子/賣房子,而是委託仲介面對買家/賣家,不自行處理大小雜事,只在最後關鍵的時刻達成交易共識。

在程式中,即是透過 MediatR 去調用不同的服務,如請求/回應、命令、查詢、通知和事件、同步和非同步等。

環境

  • FluentValidation.DependencyInjectionExtensions
  • MediatR

在 MediatR v12 之前的版本,我們必須額外安裝 MediatR.Extensions.Microsoft.DependencyInjection 套件。

安裝 MediatR

/static/CQRS與MediatR(三)_546bff6b9a254957bdf20d194f51530e/2023-10-13_15-14-49.png

修改IQueryHandlerICommandHandler介面

在 MediatR 中,不論是 Query 或是 Command,都是透過 IRequestIRequestHandler實作,但我習慣分開使用,這屬於個人偏好,此步驟可以省略。

若省略後續文章請使用IRequestIRequestHandler

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public interface ICommand<out TResponse> : IRequest<TResponse>
{
}

public interface ICommandHandler<in TCommand, TResponse> : IRequestHandler<TCommand, TResponse>
        where TCommand : ICommand<TResponse>
{
}

// OR 不回傳值
//public interface ICommand : IRequest
//{
//}

//public interface ICommandHandler<TCommand> : IRequestHandler<TCommand> where TCommand : ICommand
//{
//}
1
2
3
4
5
6
7
8
public interface IQuery<out TResponse> : IRequest<TResponse>
{
}

public interface IQueryHandler<in TQuery, TResponse> : IRequestHandler<TQuery, TResponse>
      where TQuery : IQuery<TResponse>
{
}

修改原本的CommandICommandHandler方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class CreateWeatherForecastCommandHandler : ICommandHandler<CreateWeatherForecastCommand, bool>
{
    private readonly IWeatherForecastRepository _weatherForecastRepository;
    private readonly IUnitOfWork _unitOfWork;

    public CreateWeatherForecastCommandHandler(IWeatherForecastRepository weatherForecastRepository, IUnitOfWork unitOfWork)
    {
        _weatherForecastRepository = weatherForecastRepository ?? throw new ArgumentNullException(nameof(weatherForecastRepository));
        _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
    }

    public async Task<bool> Handle(CreateWeatherForecastCommand request, CancellationToken cancellationToken)
    {
        WeatherForecast weatherForecast = new(request.Nation, request.City, request.Date, request.TemperatureC)
        {
            Summary = request.Summary,
        };

        _weatherForecastRepository.AddWeatherForecast(weatherForecast);

        await _unitOfWork.SaveChangesAsync(cancellationToken);
        return true;
    }
}

public class CreateWeatherForecastCommand : ICommand<bool>
{
    public string Nation { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public string? Summary { get; set; }
}

IServiceCollection註冊

在Application層註冊

  1. 在 Application 層,建立獨立的 DependencyInjection類別

    /static/CQRS與MediatR(三)_546bff6b9a254957bdf20d194f51530e/2023-10-11_11-35-28.png
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    public static class DependencyInjection
    {
        public static IServiceCollection AddApplication(this IServiceCollection services)
        {
            services.AddMediatR(cf => cf.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly));
    
            return services;
        }
    }
    
  2. 將擴充功能加入到 Program.csStartup.cs

    1
    
    builder.Services.AddApplication();
    

直接註冊

1
2
Assembly applicationAssemblies = typeof(CreateWeatherForecastCommandHandler).Assembly;
builder.Services.AddMediatR(cf => cf.RegisterServicesFromAssembly(applicationAssemblies));

Controller 注入IMediator

使用 MediatR 後,調用命令全部由 MediatR 負責了,所以我們不用在自行註冊 CommandHandler 服務。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly IMediator _mediator;

    // 1.加入一個初始化 IMediatR 實例的建構子
    public WeatherForecastController(IMediator mediator)
    {
        _mediator = mediator;
    }

		[HttpPost]
    public async Task<IActionResult> CreateWeatherForecast(
        [FromBody] CreateWeatherForecastRequest request,
        [FromServices] IValidator<CreateWeatherForecastCommand> validator)
    {
        var command = new CreateWeatherForecastCommand
        {
            Nation = request.Nation,
            City = request.City,
            Date = request.Date,
            TemperatureC = request.TemperatureC,
            Summary = request.Summary,
        };

        var validate = await validator.ValidateAsync(command);
        if (validate.IsValid is false) return BadRequest(validate.ToDictionary());
		// 2.使用 IMediatR 調用命令
        var result = await _mediator.Send(command);

        return result ? Ok() : BadRequest();
    }
}

參考

使用 Web API 實作微服務應用程式層

https://learn.microsoft.com/zh-tw/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/microservice-application-layer-implementation-web-api