.NET 5 源代码生成器——MediatR——CQRS
在这篇文章中,我们将探索如何使用.NET 5中的新source generator特性,使用MediatR库和CQRS模式自动为系统生成API。
中介者模式
中介模式是在应用程序中解耦模块的一种方式。在基于web的应用程序中,它通常用于将前端与业务逻辑的解耦。
在.NET平台上,MediatR库是该模式最流行的实现之一。如下图所示,中介器充当所发送命令的发送方和接收方之间的中间人。发送者不知道也不关心谁在处理命令。
使用MediatR,我们定义了一个command,它实现IRequest<T>接口,其中T表示返回类型。在这个例子中,我们有一个CreateUser类,它将返回一个字符串给调用者:
public class CreateUser : IRequest<string>
{
public string id { get; set; }
public string Name { get; set; }
}
从ASP.NET Core API发送命令到MediatR,我们可以使用以下代码:
[Route("api/[controller]")]
[ApiController]
public class CommandController : ControllerBase
{
private readonly IMediator _mediator;
public CommandController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<string> Get(CreateUser command)
{
return await _mediator.Send(command);
}
}
在接收端,实现也非常简单:创建一个实现IRequestHandler<T,U>接口的类。在本例中,我们有一个处理程序,它处理CreateUser并向调用者返回一个字符串:
public class CommandHandlers : IRequestHandler<CreateUser, string="">
{
public Task<string> Handle(CreateUser request,
CancellationToken cancellationToken)
{
return Task.FromResult("User Created");
}
}
每个处理程序类可以处理多个命令。处理规则是对于一个特定的命令,应该总是只有一个处理程序。如果希望将消息发送给许多订阅者,则应该使用MediatR中的内置通知功能,但在本例中我们将不使用该功能。
CQRS
Command Query Responsibility Segregation(CQRS)是一个非常简单的模式。它要求我们应该将系统中的命令(写)的实现与查询(读)分离开来。
有了CQRS,我们会从这样做:
改为这样做:
CQRS通常与event sourcing相关联,但是使用CQRS并不需要使用event sourcing,而仅仅使用CQRS本身就会给我们带来很多架构上的优势。这是为什么呢?因为读写的需求通常是不同的,所以它们需要单独的实现。
Mediator + CQRS
在示例应用程序中结合这两种模式,我们可以创建如下的架构:
Command和Query
使用MediatR,Command和Query之间没有明显的分离,因为两者都将实现IRequest<T>接口。为了更好地分离它们,我们将引入以下接口:
public interface ICommand<T> : IRequest<T>
{
}
public interface IQuery<T> : IRequest<T>
{
}
下面是使用这两个接口的示例:
public record CreateOrder : ICommand<string>
{
public int Id { get; set; }
public int CustomerId { get; set; }
}
public record GetOrder : IQuery<order>
{
public int OrderId { get; set; }
}
为了进一步改进我们的代码,我们可以使用新的C# 9 record特性。在内部,它仍然是一个类,但是我们为我们生成了很多样板代码,包括equality, GetHashCode, ToString……
前端Command和Query
要真正从外部接收Command和Query,我们需要创建一个ASP.NET Core API。这些action方法将接收传入的HTTP命令,并将它们传递给MediatR以进行进一步处理。控制器可能是这样的:
[Route("api/[controller]")]
[ApiController]
public class CommandController : ControllerBase
{
private readonly IMediator _mediator;
public CommandController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<string> CreateOrder([FromBody] CreateOrder command)
{
return await _mediator.Send(command);
}
[HttpPost]
public async Task<order> GetOrder([FromBody] GetOrder command)
{
return await _mediator.Send(command);
}
}
然后,MediatR将把Command和Query传递给各种处理程序,这些处理程序将处理它们并返回响应。应用CQRS模式,我们将为Command和Query处理程序使用单独的类。
public class CommandHandlers : IRequestHandler<CreateOrder, string="">
{
public Task<string> Handle(CreateOrder request, CancellationToken ct)
{
return Task.FromResult("Order created");
}
}
public class QueryHandlers : IRequestHandler<GetOrder, Order="">
{
public Task<Order> Handle(GetOrder request, CancellationToken ct)
{
return Task.FromResult(new Order()
{
Id = 2201,
CustomerId = 1234,
OrderTotal = 9.95m,
OrderLines = new List<OrderLine>()
});
}
}
源代码生成器
这是Roslyn编译器中的一个新特性,它允许我们hook到编译器,并在编译过程中生成额外的代码。
在一个非常高的层次上,你可以看到它如下:
- 首先,编译器编译你的C#源代码并生成语法树。
- 然后,源代码生成器可以检查这个语法树并生成新的C#源代码。
- 然后,这个新的源代码被编译并添加到最终的输出中。
重要的是要知道源代码生成器永远不能修改现有的代码,它只能向应用程序添加新代码。
源代码生成器+MediatR+CQRS
对于我们实现的每个Command和Query,我们必须编写相应的ASP.NET Core action方法。
这意味着如果我们的系统中有50个Command和Query,我们需要创建50个action方法。当然,这将是相当乏味的、重复的和容易出错的。
但是,如果仅仅基于Command/Query,我们就可以生成API代码作为编译的一部分,这不是很酷吗?就像这样:
意思是,如果我创建这个Command类:
/// <summary>
/// Create a new order
/// </summary>
/// <remarks>
/// Send this command to create a new order in the system for a given customer
/// </remarks>
public record CreateOrder : ICommand<string>
{
/// <summary>
/// OrderId
/// </summary>
/// <remarks>This is the customers internal ID of the order.</remarks>
/// <example>123</example>
[Required]
public int Id { get; set; }
/// <summary>
/// CustomerID
/// </summary>
/// <example>1234</example>
[Required]
public int CustomerId { get; set; }
}
然后,源生成器将生成以下类,作为编译的一部分:
/// <summary>
/// This is the controller for all the commands in the system
/// </summary>
[Route("api/[controller]/[Action]")]
[ApiController]
public class CommandController : ControllerBase
{
private readonly IMediator _mediator;
public CommandController(IMediator mediator)
{
_mediator = mediator;
}
/// <summary>
/// Create a new order
/// </summary>
/// <remarks>
/// Send this command to create a new order in the system for a given customer
/// </remarks> /// <param name="command">An instance of the CreateOrder
/// <returns>The status of the operation</returns>
/// <response code="201">Returns the newly created item</response>
/// <response code="400">If the item is null</response>
[HttpPost]
[Produces("application/json")]
[ProducesResponseType(typeof(string), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<string> CreateOrder([FromBody] CreateOrder command)
{
return await _mediator.Send(command);
}
}
使用OpenAPI生成API文档
幸运的是是Swashbuckle包含在ASP.NET Core 5的API模板默认情况下,会看到这些类并为我们生成漂亮的OpenAPI (Swagger)文档!
看看我的代码
他是这样组成的:
- SourceGenerator
这个项目包含实际的源生成器,它将生成API控制器action方法。
- SourceGenerator-MediatR-CQRS
- 这是一个使用源代码生成器的示例应用程序。查看项目文件,以了解该项目如何引用源生成器。
- Templates这个文件夹包含Command和Query类的模板。源代码生成器将把生成的代码插入到这些模板中。
- CommandAndQueries基于此文件夹中定义的Command和Query,生成器将生成相应的ASP.NET终结点。
查看生成的代码
我们如何看到生成的源代码?通过将这些行添加到API项目文件中,我们可以告诉编译器将生成的源代码写到我们选择的文件夹中:
<EmitCompilerGeneratedFiles>
True
</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>
$(BaseIntermediateOutputPath)\GeneratedFiles
</CompilerGeneratedFilesOutputPath>
这意味着你可以在这个目录中找到生成的代码:
\obj\GeneratedFiles\SourceGenerator\SourceGenerator.MySourceGenerator
在这个文件夹里你会找到以下两个文件:
结论
通过引入源代码生成器的概念,我们可以删除大量必须编写和维护的样板代码。我不是编译器工程师,我在源代码生成器方面的方法可能不是100%最优的(甚至不是100%正确的),但它仍然表明任何人都可以创建自己的源代码生成器,而没有太多麻烦。
欢迎关注我的公众号,如果你有喜欢的外文技术文章,可以通过公众号留言推荐给我。
原文链接:https://www.edument.se/en/blog/post/net-5-source-generators-mediatr-cqrs
.NET 5 源代码生成器——MediatR——CQRS的更多相关文章
- 基于SSM风格的Java源代码生成器
一.序言 UCode Cms 是一款Maven版的Java源代码生成器,是快速构建项目的利器.代码生成器模块属于可拆卸模块,即按需引入.代码生成器生成SSM(Spring.SpringBoot.Myb ...
- 【摸鱼神器】基于SSM风格的Java源代码生成器 单表生成 一对一、一对多、多对多连接查询生成
一.序言 UCode Cms 是一款Maven版的Java源代码生成器,是快速构建项目的利器.代码生成器模块属于可拆卸模块,即按需引入.代码生成器生成SSM(Spring.SpringBoot.Myb ...
- .NET Core 使用MediatR CQRS模式
前言 CQRS(Command Query Responsibility Segregation)命令查询职责分离模式,它主要从我们业务系统中进行分离出我们(Command 增.删.改)和(Query ...
- .NET Core 使用MediatR CQRS模式 读写分离
前言 CQRS(Command Query Responsibility Segregation)命令查询职责分离模式,它主要从我们业务系统中进行分离出我们(Command 增.删.改)和(Query ...
- .Net拾忆:CodeDom动态源代码生成器和编译器
代码文档模型CodeDom命名空间下主要有两个,很明显第一个代码逻辑分析,第二个负责代码的编译 using System.CodeDom; using System.CodeDom.Compiler; ...
- Delphi 源代码生成器
- 使用.NET 6开发TodoList应用(6)——使用MediatR实现POST请求
需求 需求很简单:如何创建新的TodoList和TodoItem并持久化. 初学者按照教程去实现的话,应该分成以下几步:创建Controller并实现POST方法:实用传入的请求参数new一个数据库实 ...
- Java 框架、库和软件的精选列表(awesome java)
原创翻译,原始链接 本文为awesome系列中的awesome java Awesome Java Java 框架.库和软件的精选列表 项目 Bean映射 简化 bean 映射的框架 dOOv - 为 ...
- 国外程序员整理的Java资源大全分享
Java 几乎是许多程序员们的入门语言,并且也是世界上非常流行的编程语言.国外程序员 Andreas Kull 在其 Github 上整理了非常优秀的 Java 开发资源,推荐给大家. 译文由 Imp ...
随机推荐
- 为什么要小心使用 Task.Run
昨天在博客园有园友问了我一个问题,是这样的: 先是半个月前 @碧水青荷 童鞋的一句话"大家都说不要随便 Task.Run(()=>{}) 这样写",当时没有想太多,这句话并没 ...
- Zookeeper(5)---分布式锁
基于临时序号节点来实现分布式锁 为什么要用临时节点呢?如果拿到锁的服务宕机了,会话失效ZK自己也会删除掉临时的序号节点,这样也不会阻塞其他服务. 流程: 1.在一个持久节点下面创建临时的序号节点作为锁 ...
- opencv-python imread、imshow浏览目录下的图片文件
☞ ░ 前往老猿Python博文目录 ░ 一.几个知识点 1.1.使用Python查找目录下的文件 具体请参考<Python正则表达式re模块和os模块实现文件搜索模式匹配>. 1.2.o ...
- 第8.22节 Python案例详解:重写 “富比较”方法控制比较逻辑
一. 案例说明 本节定义一个小汽车的类Car,类中包括车名carname.百公里油耗oilcostper100km.价格price三个属性.然后实现__lt__.__gt__.__le__.__ge_ ...
- Python基础篇学习感悟:学如不及,犹恐失之
从2019年3月底开始学习Python,4月12日在CSDN发表第一篇博文,时至今日已有4个月零12天. 4个多月的学习,老猿从一个Python小白成长到今天,可以说对Python这门语言已经略知一二 ...
- PyQt(Python+Qt)学习随笔:QListView的spacing属性
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QListView的spacing属性用于控制视图布局中数据项周围填充的空白空间的大小.缺省值为0, ...
- Xray高级版白嫖破解指南
啊,阿Sir,免费的还想白嫖?? 好啦好啦不开玩笑 Xray是从长亭洞鉴核心引擎中提取出的社区版漏洞扫描神器,支持主动.被动多种扫描方式,自备盲打平台.可以灵活定义 POC,功能丰富,调用简单,支持 ...
- 分别使用python和java练习冒泡排序
冒泡排序算法的运作如下:(从小到大) 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.在这一点,最后的元素应该会是最大的数. 针 ...
- 百度前端技术学院-基础-day17-18
JavaScript小练习 task 1 基于上一个任务中,关于加减乘除的任务,加上对于特殊情况的判断,比如判断两个输入框是否都是正常输入了数字类型的内容,比如除法的时候除数是否为0,当判断到输入有异 ...
- 廖雪峰官网学习js 数组
indexOf( ) 某字符的位置 slice 相当于string 的substring 切片 a = ['a','b',1,2,3] (5) ["a", "b&q ...