在 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
套件。
修改IQueryHandler
、ICommandHandler
介面
在 MediatR 中,不論是 Query 或是 Command,都是透過 IRequest
及 IRequestHandler
實作,但我習慣分開使用,這屬於個人偏好,此步驟可以省略。
若省略後續文章請使用IRequest
、IRequestHandler
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>
{
}
|
修改原本的Command
和ICommandHandler
方法
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層註冊
-
在 Application 層,建立獨立的 DependencyInjection
類別
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;
}
}
|
-
將擴充功能加入到 Program.cs
或 Startup.cs
中
1
|
builder.Services.AddApplication();
|
直接註冊
1
2
|
Assembly applicationAssemblies = typeof(CreateWeatherForecastCommandHandler).Assembly;
builder.Services.AddMediatR(cf => cf.RegisterServicesFromAssembly(applicationAssemblies));
|
使用 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