zoukankan      html  css  js  c++  java
  • .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到编译器,并在编译过程中生成额外的代码。

    在一个非常高的层次上,你可以看到它如下:

    1. 首先,编译器编译你的C#源代码并生成语法树。
    2. 然后,源代码生成器可以检查这个语法树并生成新的C#源代码。
    3. 然后,这个新的源代码被编译并添加到最终的输出中。

    重要的是要知道源代码生成器永远不能修改现有的代码,它只能向应用程序添加新代码。

    源代码生成器+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
    1. 这是一个使用源代码生成器的示例应用程序。查看项目文件,以了解该项目如何引用源生成器。
    2. Templates这个文件夹包含Command和Query类的模板。源代码生成器将把生成的代码插入到这些模板中。
    3. 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

  • 相关阅读:
    Java并发实现一(并发的实现之Thread和Runnable的区别)
    Java中的enum
    Eclipse+Maven创建webapp项目
    手机上最简洁的"云笔记"软件
    工具与艺术的结合:浅谈博客的排版规范与样式设计
    页面定制CSS代码初探(四):cnblogs使用Github引用样式
    脑图工具MindNode"附属节点"是什么意思 图解
    页面定制CSS代码初探(三):设置正文最小高度
    Sublime 是自动检测而非自动设置缩进
    苹果操作系统名称演变史 新名称macOS
  • 原文地址:https://www.cnblogs.com/hhhnicvscs/p/14209729.html
Copyright © 2011-2022 走看看