網站首頁 編程語言 正文
需求
需求很簡單:如何創建新的TodoList
和TodoItem
并持久化。
初學者按照教程去實現的話,應該分成以下幾步:創建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的角度來說,就是把R
和CUD
區分開來對待。如下圖所示:
這個模式可以有效地應用到具有主從分離的數據庫架構中,當需要獲取數據時,從只讀數據庫(一般是從庫)中讀取數據,當需要寫入或更新數據時,向主庫進行操作。
CQRS模式旨在解決的問題是:為了屏蔽數據庫層面“寫優先”還是“讀優先”的優化設計策略,在業務邏輯側進行解耦。
任何設計模式都是對解決特定問題的一個Trade off,自然也帶來了一些缺點,首先就是服務內部的組件復雜度上升了,因為需要創建額外的類來實現CQRS模式;其次如果數據層是分離的,那么可能會有數據的狀態不一致問題。
中介者Mediator模式
這是23種基本設計模式中的一個,屬于行為型設計模式,它給出了組件之間交互的一種解耦的方式。簡單參考下圖,具體內容就不過多解釋了,任何一篇介紹設計模式的文章都有介紹。
這種設計模式實際上是一種采用依賴倒置(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.Application; 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請求
在本章中我們只實現TodoList
和TodoItem
的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; } }
有一些實踐是將Request
和RequestHandler
分開兩個文件,我更傾向于像這樣將他倆放在一起,一是保持簡潔,二是當你需要順著一個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 IRepository<TodoItem> _repository; public CreateTodoItemCommandHandler(IRepository<TodoItem> repository) { _repository = repository; } public async Task<Guid> Handle(CreateTodoItemCommand request, CancellationToken cancellationToken) { var entity = new TodoItem { // 這個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驗證
請求
返回
數據庫
第一條數據是種子數據,第二條是我們剛才創建的。
創建TodoItem驗證
繼續拿剛才創建的這個TodoList的Id來創建新的TodoItem:
請求
返回
數據庫
最后一條是我們新創建的,其余是種子數據。
總結
我們已經通過演示在POST請求中實現MediatR庫帶來的CQRS模式,在這篇文章里我留了一個坑。就是領域事件的Handler并沒有任何演示,只是創建了一個文件夾,結合在這篇文章中留下來的發布領域事件的坑,會在DELETE的文章中填完。
看起來使用CQRS模式使得我們的代碼結構變得更加復雜了,但是對于一些再復雜一些的實際項目中,正確使用CQRS模式有助于你分析和整理業務需求,并將相關的業務需求以及相關模型梳理到統一的位置進行管理,包括在后續的文章里我們會陸續向其中加入諸如入參校驗、出參類型轉換等邏輯。認真思考并運用習慣之后,大家可以自行體會這樣做的“權衡”。
參考資料
原文鏈接:https://www.cnblogs.com/code4nothing/p/build-todolist-6.html
相關推薦
- 2023-04-24 python中argparse模塊及action='store_true'詳解_python
- 2023-03-28 react-redux及redux狀態管理工具使用詳解_React
- 2022-09-18 Golang?模塊引入及表格讀寫業務快速實現示例_Golang
- 2023-01-12 Redis中Bloom?filter布隆過濾器的學習_Redis
- 2022-05-12 android okHttp網絡請求封裝
- 2023-02-06 Go語言基礎學習之指針詳解_Golang
- 2023-01-19 React?classnames原理及測試用例_React
- 2022-06-09 教你在k8s上部署HADOOP-3.2.2(HDFS)的方法_云其它
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支