首頁>技術>

DDD領域驅動設計是什麼DDD領域驅動設計:實體、值物件、聚合根DDD領域驅動設計:倉儲MediatR一個優秀的.NET中介者框架2 什麼是CQRS?

CQRS,即命令和查詢職責分離,是一種分離資料讀取與寫入的體系結構模式。 基本思想是把系統劃分為兩個界限:

查詢,不改變系統的狀態,且沒有副作用。命令,更改系統狀態。

我們透過Udi Dahan的《Clarified CQRS》文章中的圖來介紹一下:

2.1 查詢 (Query)

上圖中,可以看到Query不是透過DB來查詢,而是透過一個專門用於查詢的Cache(或ReadDB),ReadDB中的表是專門針對UI最佳化過的,例如最新的產品列表,銷量最好的產品列表等,基本屬於用空間換時間。

2.2 命令 (Command)

上圖中,Command類似於Application Service,Command中主要做的事情有兩個:1、透過呼叫領域層,把相關業務資料寫入到DB中。2、同時更新ReadDB。

2.3 領域事件 (Domain Event)

上圖中,更新ReadDB有兩種方式,一種是直接在Command中進行更新,還有一種監聽領域事件,把相應更改的資料同步到ReadDB中。

3 如何實現CQRS?

我們在這裡使用最簡單的方法:只將查詢與命令分離,且執行這兩種操作時使用相同的資料庫。

3.1 命令 (Command)

首先,命令類

命令是讓系統執行更改系統狀態的操作的請求。 命令具有命令性,且應僅處理一次。

由於命令具有命令性,所以通常採用命令語氣使用謂詞(如“create”或“update”)命名,命令可能包括聚合型別,例如 CreateTodoCommand 與事件不同,命令不是過去發生的事實,它只是一個請求,因此可以拒絕它。

命令可能源自 UI,由使用者發出請求而產生,也可能來自程序管理器,由程序管理器指導聚合執行操作而產生。

命令的一個重要特徵是它應該由單一接收方處理,且僅處理一次。 這是因為命令是要在應用程式中執行的單個操作或事務。 例如,同一個“建立待辦事項”的處理次數不應超過一次。 這是命令和事件之間的一個重要區別。 事件可能會經過多次處理,因為許多系統或微服務可能會對該事件感興趣。

命令透過包含資料欄位或集合(其中包含執行命令所需的所有資訊)的類實現。 命令是一種特殊的資料傳輸物件 (DTO),專門用於請求更改或事務。 命令本身完全基於處理命令所需的資訊,別無其他。

下面的示例顯示了簡化的 CreateTodoCommand 類。

public class CreateTodoCommand : IRequest<TodoDTO>{    public Guid Id { get; set; }    public string Name { get; set; }}

然後,命令處理程式類

應為每個命令實現特定命令處理程式類。 這是該模式的工作原理,是應用命令物件、域物件和基礎結構儲存庫物件的情景。

命令處理程式收到命令,並從使用的聚合獲取結果。 結果應為成功執行命令,或者異常。 出現異常時,系統狀態應保持不變。

命令處理程式通常執行以下步驟:

它接收 DTO 等命令物件。它會驗證命令是否有效。它會例項化作為當前命令目標的聚合根例項。它會在聚合根例項上執行方法,從命令獲得所需資料。它將聚合的新狀態保持到相關資料庫。

通常情況下,命令處理程式處理由聚合根(根實體)驅動的單個聚合。 如果多個聚合應受到單個命令接收的影響,可使用域事件跨多個聚合傳播狀態或操作。

作為命令處理程式類的示例,下面的程式碼演示本章開頭介紹的同一個 CreateTodoCommandHandler 類。 這個示例還強調了 Handle 方法以及域模型物件/聚合的操作。

public class CreateTodoCommandHandler        : IRequestHandler<CreateTodoCommand, TodoDTO>{    private readonly IRepository repository;    private readonly IMapper mapper;    public CreateTodoCommandHandler(IRepository repository, IMapper mapper)    {        this.repository = repository ?? throw new ArgumentNullException(nameof(repository));        this.mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));    }    public async Task<TodoDTO> Handle(CreateTodoCommand message, CancellationToken cancellationToken)    {        var todo = Todo.Create(message.Name);        repository.Entry(todo);        await repository.SaveAsync();        var todoForDTO = mapper.Map<TodoDTO>(todo);        return todoForDTO;    }}

最後,透過MediatR實現命令程序管道首先,讓我們看一下示例 WebAPI 控制器,你會在其中使用MediatR,如以下示例所示:

[Route("api/[controller]")][ApiController]public class TodosController : ControllerBase{    //...    private readonly MediatR.IMediator mediator;    public TodosController(MediatR.IMediator mediator)    {        this.mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));    }    //...}

在控制器方法中,將命令傳送到MediatR的程式碼幾乎只有一行:

[HttpPost]public async Task<ActionResult<TodoDTO>> Create(CreateTodoCommand param){    var ret = await mediator.Send(param);    return CreatedAtAction(nameof(Get), new { id = ret.Id }, ret);}
3.2 查詢 (Query)

首先,定義DTO

[Table("T_Todo")]public class TodoDTO{    #region Public Properties    public Guid Id { get; set; }    public string Name { get; set; }    #endregion                }

然後,建立具體的查詢方法

public class TodoQueries{    private readonly TodoingQueriesContext context;    public TodoQueries(TodoingQueriesContext context)    {        this.context = context;    }    //...    public async Task<PaginatedItems<TodoDTO>> Query(int pageIndex, int pageSize)    {        var total = await context.Todos            .AsNoTracking()            .CountAsync();        var todos = await context.Todos            .AsNoTracking()            .OrderBy(o => o.Id)            .Skip(pageSize * (pageIndex - 1))            .Take(pageSize)            .ToListAsync();        return new PaginatedItems<TodoDTO>(total, todos);    }    //...}

請注意TodoingQueriesContext和命令處理中的Context不是同一個,實現查詢端除了用EFCore、還可以用儲存過程、檢視、具體化檢視或Dapper等等。

最後,呼叫查詢方法

[Route("api/[controller]")][ApiController]public class TodosController : ControllerBase{    private readonly TodoQueries todoQueries;    public TodosController(TodoQueries todoQueries)    {        this.todoQueries = todoQueries ?? throw new ArgumentNullException(nameof(todoQueries));    }    //...    [HttpGet]    public async Task<ActionResult<PaginatedItems<TodoDTO>>> Query(int pageIndex, int pageSize)    {        return todoQueries.Query(pageIndex, pageSize).Result;    }    //...}

16
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 騷操作 | 高效辦公,Python 自動化教你一鍵獲取日誌