在这篇文章中,咱们将摸索如何应用.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>
这意味着你能够在这个目录中找到生成的代码:
objGeneratedFilesSourceGeneratorSourceGenerator.MySourceGenerator
在这个文件夹里你会找到以下两个文件:
论断
通过引入源代码生成器的概念,咱们能够删除大量必须编写和保护的样板代码。我不是编译器工程师,我在源代码生成器方面的办法可能不是100%最优的(甚至不是100%正确的),但它依然表明任何人都能够创立本人的源代码生成器,而没有太多麻烦。
欢送关注我的公众号,如果你有喜爱的外文技术文章,能够通过公众号留言举荐给我。