.NET 6开发TodoList应用之使用MediatR实现POST请求
需求
需求很简单:如何创建新的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模式有助于你分析和整理业务需求,并将相关的业务需求以及相关模型梳理到统一的位置进行管理,包括在后续的文章里我们会陆续向其中加入诸如入参校验、出参类型转换等逻辑。认真思考并运用习惯之后,大家可以自行体会这样做的“权衡”。
参考资料
以上就是.net 6开发todolist应用之使用mediatr实现post请求的详细内容,更多关于.net 6 mediatr实现post请求的资料请关注其它相关文章!