在這篇文章中,咱們將探索如何使用.NET 5中的新source generator特性,使用MediatR庫和CQRS模式自動爲系統生成API。前端
中介者模式web
中介模式是在應用程序中解耦模塊的一種方式。在基於web的應用程序中,它一般用於將前端與業務邏輯的解耦。json
在.NET平臺上,MediatR庫是該模式最流行的實現之一。以下圖所示,中介器充當所發送命令的發送方和接收方之間的中間人。發送者不知道也不關心誰在處理命令。api
使用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,咱們可使用如下代碼:app
[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並向調用者返回一個字符串:async
public class CommandHandlers : IRequestHandler<CreateUser, string=""> { public Task<string> Handle(CreateUser request, CancellationToken cancellationToken) { return Task.FromResult("User Created"); } }
每一個處理程序類能夠處理多個命令。處理規則是對於一個特定的命令,應該老是隻有一個處理程序。若是但願將消息發送給許多訂閱者,則應該使用MediatR中的內置通知功能,但在本例中咱們將不使用該功能。post
CQRSui
Command Query Responsibility Segregation(CQRS)是一個很是簡單的模式。它要求咱們應該將系統中的命令(寫)的實現與查詢(讀)分離開來。this
有了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到編譯器,並在編譯過程當中生成額外的代碼。
在一個很是高的層次上,你能夠看到它以下:
重要的是要知道源代碼生成器永遠不能修改現有的代碼,它只能嚮應用程序添加新代碼。
源代碼生成器+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)文檔!
看看個人代碼
他是這樣組成的:
這個項目包含實際的源生成器,它將生成API控制器action方法。
查看生成的代碼
咱們如何看到生成的源代碼?經過將這些行添加到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