使用 MediatR 實作 CQRS
關於 CQRS 模式,推薦閱讀 CQRS (Command Query Responsibility Segregation). By Ajay Kumar。書中的範例專案是用 Java 撰寫,但只要熟悉 C# 或物件導向程式語言的讀者,都可以很容易理解。此書以學生選課管理系統為範例,透過重構為任務型介面,再進一步重構為 CQRS 模式,來說明主要概念。此書也是以實務的角度出發,提到了很多權衡,例如建立查詢專用資料庫的必要性。
基礎配置
建立
.NET 8
專案Purchasing.Core
(Class Library)Purchasing.Infrastructure
(Class Library)Purchasing.UseCases
(Class Library)Purchasing.Api
(Web API)
安裝
MediatR
Nuget Packages版本為
12.2.0
,安裝到Purchasing.UseCases
、Purchasing.Api
。1
2Install-Package MediatR -Project Purchasing.UseCases
Install-Package MediatR -Project Purchasing.Api配置
MediatR
在
Purchasing.UseCases
新增下列程式碼:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// Extensions/DependencyInjection/ServiceCollectionExtensions.cs
using Microsoft.Extensions.DependencyInjection;
namespace Purchasing.UseCases.Extensions.DependencyInjection
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMediatR(this IServiceCollection services)
{
services.AddMediatR(cfg =>
cfg.RegisterServicesFromAssembly(typeof(ServiceCollectionExtensions).Assembly));
return services;
}
}
}在
Purchasing.Api
新增下列程式碼:1
2
3
4
5
6
7
8
9
10
11// Program.cs
// ...
using Purchasing.UseCases.Extensions.DependencyInjection
// ...
builder.Services.AddMediatR();
// ...
建立 Query
在
Purchasing.UseCases
新增Query
1
2
3
4
5
6
7
8// Queries/ListPurchaseOrdersQuery.cs
using MediatR;
namespace Purchasing.UseCases.Queries
{
public record ListPurchaseOrdersQuery(string? UserId) : IRequest<List<PurchaseOrderDto>>;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// Handlers/ListPurchaseOrdersHandler.cs
using MediatR;
using Purchasing.UseCases.Contracts;
using Purchasing.UseCases.Queries;
namespace Purchasing.UseCases.Handlers
{
public class ListPurchaseOrdersHandler: IRequestHandler<ListPurchaseOrdersQuery, List<PurchaseOrderDto>>
{
public async Task<List<PurchaseOrderDto>> Handle(ListPurchaseOrdersQuery request, CancellationToken cancellationToken)
{
// ...
}
}
}
建立 Command
在
Purchasing.UseCases
新增Command
1
2
3
4
5
6
7
8// Commands/CreatePurchaseOrderCommand.cs
using MediatR;
namespace Purchasing.UseCases.Commands
{
public record CreatePurchaseOrderCommand(string UserId) : IRequest<int>;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// Handlers/CreatePurchaseOrderHandler.cs
using MediatR;
using Purchasing.UseCases.Commands;
namespace Purchasing.UseCases.Handlers
{
public class CreatePurchaseOrderHandler: IRequestHandler<CreatePurchaseOrderCommand, int>
{
public async Task<int> Handle(CreatePurchaseOrderCommand request, CancellationToken cancellationToken)
{
// ...
}
}
}
建立 Controller
在
Purchasing.Api
新增Controller
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
35
36
37
38// Controllers/PurchaseOrdersController.cs
using Purchasing.UseCases.Commands;
using Purchasing.UseCases.Contracts;
using Purchasing.UseCases.Queries;
using MediatR;
using Microsoft.AspNetCore.Mvc;
namespace Purchasing.Api.Controllers
{
[ ]
[ ]
public class PurchaseOrdersController : ControllerBase
{
private readonly IMediator _mediator;
public PurchaseOrdersController(IMediator mediator)
{
_mediator = mediator;
}
[ ]
public async Task<ActionResult<List<PurchaseOrderDto>>> ListPurchaseOrdersAsync([FromQuery] ListPurchaseOrdersQuery query)
{
var purchaseOrders = await _mediator.Send(query);
return purchaseOrders;
}
[ ]
public async Task<ActionResult<int>> CreatePurchaseOrderAsync([FromBody] CreatePurchaseOrderCommand command)
{
var purchaseOrderId = await _mediator.Send(command);
return purchaseOrderId;
}
}
}