乐趣区

关于c#:NET-5-源代码生成器MediatRCQRS

在这篇文章中,咱们将摸索如何应用.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>

 这意味着你能够在这个目录中找到生成的代码:

objGeneratedFilesSourceGeneratorSourceGenerator.MySourceGenerator

在这个文件夹里你会找到以下两个文件:

论断

通过引入源代码生成器的概念,咱们能够删除大量必须编写和保护的样板代码。我不是编译器工程师,我在源代码生成器方面的办法可能不是 100% 最优的(甚至不是 100% 正确的),但它依然表明任何人都能够创立本人的源代码生成器,而没有太多麻烦。

 欢送关注我的公众号,如果你有喜爱的外文技术文章,能够通过公众号留言举荐给我。

退出移动版