網(wǎng)站首頁 編程語言 正文
.NET?6開發(fā)TodoList應(yīng)用之使用MediatR實(shí)現(xiàn)POST請(qǐng)求_實(shí)用技巧
作者:CODE4NOTHING ? 更新時(shí)間: 2022-03-18 編程語言需求
需求很簡單:如何創(chuàng)建新的TodoList
和TodoItem
并持久化。
初學(xué)者按照教程去實(shí)現(xiàn)的話,應(yīng)該分成以下幾步:創(chuàng)建Controller
并實(shí)現(xiàn)POST
方法;實(shí)用傳入的請(qǐng)求參數(shù)new
一個(gè)數(shù)據(jù)庫實(shí)體對(duì)象;調(diào)用IRepository<T>
完成數(shù)據(jù)庫的寫入,最多會(huì)在中間加一層Service
。這個(gè)做法本身沒有問題,也是需要從初學(xué)階段開始扎實(shí)地掌握開發(fā)技能的必經(jīng)之路,有助于幫助理解邏輯調(diào)用的過程。
對(duì)于稍微正式一些的項(xiàng)目,.NET工程上習(xí)慣的實(shí)現(xiàn)是通過使用一些比較成熟的類庫框架,有效地對(duì)業(yè)務(wù)邏輯進(jìn)行分類管理、消除冗余代碼,以達(dá)到業(yè)務(wù)邏輯職責(zé)清晰簡潔的目的。在這個(gè)階段我們經(jīng)常使用的兩個(gè)類庫分別是AutoMapper和MediatR,本文結(jié)合POST
請(qǐng)求,先介紹關(guān)于MediatR
部分,下一篇關(guān)于GET
請(qǐng)求,會(huì)涉及AutoMapper
的部分。
目標(biāo)
合理組織并使用MediatR,完成POST
請(qǐng)求。
原理與思路
首先來簡單地介紹一下這個(gè)類庫。
關(guān)于CQRS模式、中介者模式和MediatR
CQRS模式
CQRS模式全稱是“Command Query Responsibility Segregation”,正如字面意思,CQRS模式的目的在于將讀取操作和寫入操作的指責(zé)區(qū)分開,并使用不同的Model去表示。從CRUD的角度來說,就是把R
和CUD
區(qū)分開來對(duì)待。如下圖所示:
這個(gè)模式可以有效地應(yīng)用到具有主從分離的數(shù)據(jù)庫架構(gòu)中,當(dāng)需要獲取數(shù)據(jù)時(shí),從只讀數(shù)據(jù)庫(一般是從庫)中讀取數(shù)據(jù),當(dāng)需要寫入或更新數(shù)據(jù)時(shí),向主庫進(jìn)行操作。
CQRS模式旨在解決的問題是:為了屏蔽數(shù)據(jù)庫層面“寫優(yōu)先”還是“讀優(yōu)先”的優(yōu)化設(shè)計(jì)策略,在業(yè)務(wù)邏輯側(cè)進(jìn)行解耦。
任何設(shè)計(jì)模式都是對(duì)解決特定問題的一個(gè)Trade off,自然也帶來了一些缺點(diǎn),首先就是服務(wù)內(nèi)部的組件復(fù)雜度上升了,因?yàn)樾枰獎(jiǎng)?chuàng)建額外的類來實(shí)現(xiàn)CQRS模式;其次如果數(shù)據(jù)層是分離的,那么可能會(huì)有數(shù)據(jù)的狀態(tài)不一致問題。
中介者M(jìn)ediator模式
這是23種基本設(shè)計(jì)模式中的一個(gè),屬于行為型設(shè)計(jì)模式,它給出了組件之間交互的一種解耦的方式。簡單參考下圖,具體內(nèi)容就不過多解釋了,任何一篇介紹設(shè)計(jì)模式的文章都有介紹。
這種設(shè)計(jì)模式實(shí)際上是一種采用依賴倒置(Inversion of Control, IoC)的方式,實(shí)現(xiàn)了圖中藍(lán)色組件的松耦合。
MediatR
這是在開發(fā)中被廣泛采用的實(shí)現(xiàn)以上兩種設(shè)計(jì)模式的類庫,更準(zhǔn)確的說法是,它通過應(yīng)用中介者模式,實(shí)現(xiàn)了進(jìn)程內(nèi)CQRS。基本思想是所有來自API接口和數(shù)據(jù)存儲(chǔ)之間的邏輯,都需要通過MediatR來組織(即所謂的“中介者”)。
從實(shí)現(xiàn)上看,MediatR提供了幾組用于不同場景的接口,我們?cè)诒疚闹刑幚淼谋容^多的是IRequest<T>/IRequestHandler<T>
以及INotification<T>/INotificationHander<T>
兩組接口,更多的請(qǐng)參考官方文檔和例子。
實(shí)現(xiàn)
所有需要使用MediatR的地方都集中在Application項(xiàng)目中。
引入MediatR
$ dotnet add src/TodoList.Application/TodoList.Application.csproj package MediatR.Extensions.Microsoft.DependencyInjection
為了適配CQRS的模式,我們?cè)贏pplication項(xiàng)目中的TodoLists和TodoItems下相同地創(chuàng)建幾個(gè)文件夾:
Commands
:用于組織CUD相關(guān)的業(yè)務(wù)邏輯;
Queries
:用于組織R相關(guān)的業(yè)務(wù)邏輯;
EventHandlers
:用于組織領(lǐng)域事件處理的相關(guān)業(yè)務(wù)邏輯。
在Application
根目錄下同樣創(chuàng)建DependencyInjection.cs
用于該項(xiàng)目的依賴注入:
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項(xiàng)目中使用:
// 省略其他... // 添加應(yīng)用層配置 builder.Services.AddApplication(); // 添加基礎(chǔ)設(shè)施配置 builder.Services.AddInfrastructure(builder.Configuration);
實(shí)現(xiàn)Post請(qǐng)求
在本章中我們只實(shí)現(xiàn)TodoList
和TodoItem
的Create接口(POST),剩下的接口后面的文章中逐步涉及。
POST TodoList
在Application/TodoLists/Commands/
下新建一個(gè)目錄CreateTodoList
用于存放創(chuàng)建一個(gè)TodoList
相關(guān)的所有邏輯:
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; } }
有一些實(shí)踐是將Request
和RequestHandler
分開兩個(gè)文件,我更傾向于像這樣將他倆放在一起,一是保持簡潔,二是當(dāng)你需要順著一個(gè)Command去尋找它對(duì)應(yīng)的Handler時(shí),不需要更多的跳轉(zhuǎn)。
接下來在TodoListController里實(shí)現(xiàn)對(duì)應(yīng)的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); // 出于演示的目的,這里只返回創(chuàng)建出來的TodoList的Id, // 實(shí)際使用中可能會(huì)選擇IActionResult作為返回的類型并返回CreatedAtRoute對(duì)象, // 因?yàn)槲覀冞€沒有去寫GET方法,返回CreatedAtRoute會(huì)報(bào)錯(cuò)(找不到對(duì)應(yīng)的Route),等講完GET后會(huì)在那里更新 return createdTodoList.Id; } }
POST TodoItem
類似TodoListController和CreateTodoListCommand的實(shí)現(xiàn),這里我直接把代碼貼出來了。
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 { // 這個(gè)ListId在前文中的代碼里漏掉了,需要添加到Domain.Entities.TodoItem實(shí)體上 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); // 處于演示的目的,這里只返回創(chuàng)建出來的TodoItem的Id,理由同前 return createdTodoItem.Id; } }
驗(yàn)證
運(yùn)行Api項(xiàng)目,通過Hoppscotch發(fā)送對(duì)應(yīng)接口請(qǐng)求:
創(chuàng)建TodoList驗(yàn)證
請(qǐng)求
返回
數(shù)據(jù)庫
第一條數(shù)據(jù)是種子數(shù)據(jù),第二條是我們剛才創(chuàng)建的。
創(chuàng)建TodoItem驗(yàn)證
繼續(xù)拿剛才創(chuàng)建的這個(gè)TodoList的Id來創(chuàng)建新的TodoItem:
請(qǐng)求
返回
數(shù)據(jù)庫
最后一條是我們新創(chuàng)建的,其余是種子數(shù)據(jù)。
總結(jié)
我們已經(jīng)通過演示在POST請(qǐng)求中實(shí)現(xiàn)MediatR庫帶來的CQRS模式,在這篇文章里我留了一個(gè)坑。就是領(lǐng)域事件的Handler并沒有任何演示,只是創(chuàng)建了一個(gè)文件夾,結(jié)合在這篇文章中留下來的發(fā)布領(lǐng)域事件的坑,會(huì)在DELETE的文章中填完。
看起來使用CQRS模式使得我們的代碼結(jié)構(gòu)變得更加復(fù)雜了,但是對(duì)于一些再復(fù)雜一些的實(shí)際項(xiàng)目中,正確使用CQRS模式有助于你分析和整理業(yè)務(wù)需求,并將相關(guān)的業(yè)務(wù)需求以及相關(guān)模型梳理到統(tǒng)一的位置進(jìn)行管理,包括在后續(xù)的文章里我們會(huì)陸續(xù)向其中加入諸如入?yún)⑿r?yàn)、出參類型轉(zhuǎn)換等邏輯。認(rèn)真思考并運(yùn)用習(xí)慣之后,大家可以自行體會(huì)這樣做的“權(quán)衡”。
參考資料
原文鏈接:https://www.cnblogs.com/code4nothing/p/build-todolist-6.html
相關(guān)推薦
- 2022-11-06 MobLink?Android端業(yè)務(wù)場景簡單說明_Android
- 2023-01-10 Go語言defer與return執(zhí)行的先后順序詳解_Golang
- 2022-09-25 Identity Server4/生產(chǎn)模式/證書/certificate/AddSigningCre
- 2022-05-31 Python中用戶輸入與while循環(huán)詳情_python
- 2021-09-01 Go語言集成開發(fā)環(huán)境IDE詳細(xì)安裝教程_Golang
- 2024-02-29 UNI-APP開發(fā)之插件安裝失敗,離線安裝
- 2022-09-24 基于Pytorch實(shí)現(xiàn)邏輯回歸_python
- 2022-04-19 基于HarmonyOS 的ArkUI編寫的社區(qū)類app(一)----隱私服務(wù)條款界面
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支