1. 程式人生 > 程式設計 >.NET 6開發TodoList應用之使用MediatR實現POST請求

.NET 6開發TodoList應用之使用MediatR實現POST請求

目錄
  • 需求
  • 目標
  • 原理與思路
    • CQRS模式
    • 中介者Mediator模式
    • MediatR
  • 實現
    • 引入MediatR
    • 實現Post請求
  • 驗證
    • 建立TodoList驗證
    • 建立TodoItem驗證
  • 總結
    • 參考資料

      需求

      需求很簡單:如何建立新的TodoListTodoItem並持久化。

      初學者按照教程去實現的話,應該分成以下幾步:建立Controller並實現POST方法;實用傳入的請求引數new一個實體物件;呼叫IRepository<T>完成資料庫的寫入,最多會在中間加一層Service。這個做法本身沒有問題,也是需要從初學階段開始紮實地掌握開發技能的必經之路,有助於幫助理解邏輯呼叫的過程。

      對於稍微正式一些的專案,.NET工程上習慣的實現是通過使用一些比較成熟的類庫框架,有效地對業務邏輯進行分類管理、消除冗餘程式碼,以達到業務邏輯職責清晰簡潔的目的。在這個階段我們經常使用的兩個類庫分別是AutoMapper和MediatR,本文結合POST

      請求,先介紹關於MediatR部分,下一篇關於GET請求,會涉及AutoMapper的部分。

      目標

      合理組織並使用MediatR,完成POST請求。

      原理與思路

      首先來簡單地介紹一下這個類庫。

      關於CQRS模式、中介者模式和MediatR

      CQRS模式

      CQRS模式全稱是“Command Query Responsibility Segregation”,正如字面意思,CQRS模式的目的在於將讀取操作和寫入操作的指責區分開,並使用不同的Model去表示。從CRUD的角度來說,就是把RCUD區分開來對待。如下圖所示:

      .NET6開發TodoList應用之使用MediatR實現POST請求

      這個模式可以有效地應用到具有主從分離的資料庫架構中,當需要獲取資料時,從只讀資料庫(一般是從庫)中讀取資料,當需要寫入或更新資料時,向主庫進行操作。

      CQRS模式旨在解決的問題是:為了遮蔽資料庫層面“寫優先”還是“讀優先”的優化設計策略,在業務邏輯側進行解耦。

      任何設計模式都是對解決特定問題的一個Trade off,自然也帶來了一些缺點,首先就是服務內部的元件複雜度上升了,因為需要建立額外的類來實現CQRS模式;其次如果資料層是分離的,那麼可能會有資料的狀態不一致問題。

      中介者Mediator模式

      這是23種基本設計模式中的一個,屬於行為型設計模式,它給出了元件之間互動的一種解耦的方式。簡單參考下圖,具體內容就不過多解釋了,任何一篇介紹設計模式的文章都有介紹。

      .NET6開發TodoList應用之使用MediatR實現POST請求

      這種設計模式實際上是一種採用依賴倒置(Inversion of Control,IoC)的方式,實現了圖中藍色元件的鬆耦合。

      MediatR

      這是在開發中被廣泛採用的實現以上兩種設計模式的類庫,更準確的說法是,它通過應用中介者模式,實現了程序內CQRS。基本思想是所有來自API介面和資料儲存之間的邏輯,都需要通過MediatR來組織(即所謂的“中介者”)。

      從實現上看,MediatR提供了幾組用於不同場景的介面,我們在本文中處理的比較多的是IRequest<T>/IRequestHandler<T>以及INotification<T>/INotificationHander<T>兩組介面,更多的請參考官方文件和例子。

      實現

      所有需要使用MediatR的地方都集中在Application專案中。

      引入MediatR

      $ dotnet add src/TodoList.Application/TodoList.Application.csproj package MediatR.Extensions.Microsoft.DependencyInjection
      

      為了適配CQRS的模式,我們在Application專案中的TodoLists和TodoItems下相同地建立幾個資料夾:

      Commands:用於組織CUD相關的業務邏輯;

      Queries:用於組織R相關的業務邏輯;

      EventHandlers:用於組織領域事件處理的相關業務邏輯。

      Application根目錄下同樣建立DependencyInjection.cs用於該專案的依賴注入:

      DependencyInjection.cs

      using System.Reflection;
      
      using Microsoft.Extensions.DependencyInjection;
      
      
      
      namespace TodoList.Auurnupplication;
      
      
      
      public static class DependencyInjection
      
      {
      
          public static IServiceCollection AddApplication(this IServiceCollection services)
      
          {
      
              services.AddMediatR(Assembly.GetExecutingAssembly());
      
              return services;
      
          }
      
      }
      

      並在Api專案中使用:

      // 省略其他...
      // 新增應用層配置
      builder.Services.AddApplication();
      // 新增基礎設施配置
      builder.Services.AddInfrastructure(builder.Configuration);
      

      實現Post請求

      在本章中我們只實現TodoListTodoItem的Create介面(POST),剩下的介面後面的文章中逐步涉及。

      POST TodoList

      Application/TodoLists/Commands/下新建一個目錄CreateTodoList用於存放建立一個TodoList相關的所有邏輯:

      CreateTodoListCommand.cs

      using MediatR;
      
      using TodoList.Application.Common.Interfaces;
      
      
      
      namespace TodoList.Application.TodoLists.Commands.CreateTodoList;
      
      
      
      public class CreateTodoListCommand : IRequest<Guid>
      
      {
      
          public string? Title { get; set; }
      
      }
      
      
      
      public class CreateTodoListCommandHandler : IRequestHandler<CreateTodoListCommand,Guid>
      
      {
      
          private readonly IRepository<Domain.Entities.TodoList> _repository;
      
      
      
          public CreateTodoListCommandHandler(IRepository<Domain.Entities.TodoList> repository)
      
          {
      
              _repository = repository;
      
          }
      
      
      
          public async Task<Guid> Handle(CreateTodoListCommand request,CancellationToken cancellationToken)
      
          {
      
              var entity = new Domain.Entities.TodoList
      
              {
      
                  Title = request.Title
      
              };
      
      
      
              await _repository.AddAsync(entity,cancellationToken);
      
              return entity.Id;
      
          }
      
      }
      

      有一些實踐是將RequestRequestHandler分開兩個檔案,我更傾向於像這樣將他倆放在一起,一是保持簡潔,二是當你需要順著一個Command去尋找它對應的Handler時,不需要更多的跳轉。

      接下來在TodoListController裡實現對應的POST方法,

      using MediatR;
      
      using Microsoft.AspNetCore.Mvc;
      
      using TodoList.Application.TodoLists.Commands.CreateTodoList;
      
      
      
      namespace TodoList.Api.Controllers;
      
      
      
      [ApiController]
      
      [Route("/todo-list")]
      
      public class TodoListController : ControllerBase
      
      {
      
          private readonly IMediator _mediator;
      
      
      
          // 注入MediatR
      
          public TodoListController(IMediator mediator)
      
              => _mediator = mediator;
      
      
      
          [HttpPost]
      
          public async Task<Guid> Create([FromBody] CreateTodoListCommand command)
      
          {
      
              var createdTodoList = await _mediator.Send(command);
      
      
      
              // 出於演示的目的,這裡只返回創建出來的TodoList的Id,
      
              // 實際使用中可能會選擇IActionResult作為返回的型別並返回CreatedAtRoute物件,
      
              // 因為我們還沒有去寫GET方法,返回CreatedAtRoute會報錯(找不到對應的Route),等講完GET後會在那裡更新
      
              return createdTodoList.Id;
      
          }
      
      }
      

      POST TodoItem

      類似TodoListController和CreateTodoListCommand的實現,這裡我直接把程式碼貼出來了。

      CreateTodoItemCommand.cs

      using MediatR;
      
      using TodoList.Application.Common.Interfaces;
      
      using TodoList.Domain.Entities;
      
      using TodoList.Domain.Events;
      
      
      
      namespace TodoList.Application.TodoItems.Commands.CreateTodoItem;
      
      
      
      public class CreateTodoItemCommand : IRequest<Guid>
      
      {
      
          public Guid ListId { get; set; }
      
      
      
          public string? Title { get; set; }
      
      }
      
      
      
      public class CreateTodoItemCommandHandler : IRequestHandler<CreateTodoItemCommand,Guid>
      
      {
      
          private readonly IRepohttp://www.cppcns.comsitory<TodoItem> _repository;
      
      
      
          public CreateTodoItemCommandHandler(IRepository<TodoItem> repository)
      
          {
      
              _repository = repository;
      
          }
      
      
      
          public async Task<Guid> Handle(CreateTodoItemCommand request,CancellationToken cancellationToken)
      
          {
      
              var entity = new TodoItem
      
      uurnu        {
      
                  // 這個ListId在前文中的程式碼裡漏掉了,需要新增到Domain.Entities.TodoItem實體上
      
                  ListId = request.ListId,Title = request.Title,Done = false
      
              };
      
      
      
              await _repository.AddAsync(entity,cancellationToken);
      
      
      
              return entity.Id;
      
          }
      
      }
      

      TodoItemController.cs

      using MediatR;
      
      using Microsoft.AspNetCore.Mvc;
      
      using TodoList.Application.TodoItems.Commands.CreateTodoItem;
      
      
      
      namespace TodoList.Api.Controllers;
      
      
      
      [ApiController]
      
      [Route("/todo-item")]
      
      public class TodoItemController : ControllerBase
      
      {
      
          private readonly IMediator _mediator;
      
      
      
          // 注入MediatR
      
          public TodoItemController(IMediator mediator) 
      
              => _mediator = mediator;
      
      
      
          [HttpPost]
      
          public async Task<Guid> Create([FromBody] CreateTodoItemCommand command)
      
          {
      
              var createdTodoItem = await _mediator.Send(command);
      
      
      
              // 處於演示的目的,這裡只返回創建出來的TodoItem的Id,理由同前
      
              return createdTodoItem.Id;
      
          }
      
      }
      

      驗證

      執行Api專案,通過Hoppscotch傳送對應介面請求:

      建立TodoList驗證

      請求

      .NET6開發TodoList應用之使用MediatR實現POST請求

      返回

      .NET6開發TodoList應用之使用MediatR實現POST請求

      資料庫

      .NET6開發TodoList應用之使用MediatR實現POST請求

      第一條資料是種子資料,第二條是我們剛才建立的。

      建立TodoItem驗證

      繼續拿剛才建立的這個TodoList的Id來建立新的TodoItem:

      請求

      .NET6開發TodoList應用之使用MediatR實現POST請求

      返回

      .NET6開發TodoList應用之使用MediatR實現POST請求

      資料庫

      .NET6開發TodoList應用之使用MediatR實現POST請求

      最後一條是我們新建立的,其餘是種子資料。

      總結

      我們已經通過演示在POST請求中實現MediatR庫帶來的CQRS模式,在這篇文章裡我留了一個坑。就是領域事件的Handler並沒有任何演示,只是建立了一個資料夾,結合在這篇文章中留下來的釋出領域事件的坑,會在DELETE的文章中填完。

      看起來使用CQRS模式使得我們的程式碼結構變得更加複雜了,但是對於一些再複雜一些的實際專案中,正確使用CQRS模式有助於你分析和整理業務需求,並將相關的業務需求以及相關模型梳理到統一的位置進行管理,包括在後續的文章裡我們會陸續向其中加入諸如入參校驗、出參型別轉換等邏輯。認真思考並運用習慣之後,大家可以自行體會這樣做的“權衡”。

      參考資料

      MediatR

      Mediator

      以上就是.NET 6開發TodoList應用之使用MediatR實現POST請求的詳細內容,更多關於.NET 6 MediatR實現POST請求的資料請關注我們其它相關文章!