標題:使用MediatR重構單體應用中的事件發布/訂閱
作者:Lamond Lu
地址:https://www.cnblogs.com/lwqlun/p/10640280.html
源代碼:https://github.com/lamondlu/EventHandlerInSingleApplication
背景
在之前的一篇文章中,我分享了一個在ASP.NET Core單體程序中,使用事件發布/訂閱解耦業務邏輯的例子。
項目源代碼地址:https://github.com/lamondlu/EventHandlerInSingleApplication
在文章評論中老張提到了使用MediatR的方案。對於MediatR,我以前只是聽說的,沒有認真研究過。上周末的膠東開發者技術沙龍中,衣哥也提到了這個庫,閑暇時間我就研究了一下,並修改了之前的例子,發現確實簡化了不少代碼。
如果沒有看過之前的文章,建議你先看一下之前的實現,本文中的所有修改都是針對上一篇的代碼。
中介者模式
中介者模式,定義了一個中介對象來封裝一系列對象之間的交互關系。中介者使各個對象之間不需要顯式地相互引用,從而使耦合性降低,而且可以獨立地改變它們之間的交互行為。
中介者模式是一種對象行為型模式,其主要優點如下。
- 降低了對象之間的耦合性,使得對象易於獨立地被復用。
- 將對象間的一對多關聯轉變為一對一的關聯,提高系統的靈活性,使得系統易於維護和擴展。
其實事件發布/訂閱就是中介者模式的一種實現方式。
什么是MediatR
MediatR是一個基於.NET的中介者模式實現庫,它是一種進程內消息傳遞的方案,官網地址https://github.com/jbogard/MediatR/。
MediatR可以發送兩種消息
- 請求/響應消息,這種消息只有一個處理程序, 這種方式的消息需要實現
IRequest
接口, 其處理程序需要實現IRequestHandler
接口 - 通知消息,這種消息可以有一個或多個處理程序,這種方式的消息需要實現
INotification
接口, 其處理程序需要實現INotificationHandler
接口
從消息的特性上看,如果要改造我們之前的事件發布/訂閱功能,我們需要使用通知消息,因為每個事件可能會有一個或多個的處理程序。
添加MediatR
在.NET Core中可以直接使用Nuget添加MediatR.Extensions.Microsoft.DependencyInjection庫來引入MediatR
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
添加完成后,我們還需要在Startup.cs中啟動MediatR中間件。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
...
services.AddMediatR();
}
現在我們就可以在項目中使用MediatR了。
提示:
這里你可以會有疑問,之前的代碼中,我們這里還定義了事件和處理器之間的映射,現在怎么就不需要了?
EventHandlerContainer .Subscribe<ShoppingCartSubmittedEvent, CreateOrderHandler>(); EventHandlerContainer .Subscribe<ShoppingCartSubmittedEvent, ConfirmEmailSentHandler>();
這里MediatR中已經提供了一個自動映射功能,它會在程序啟動時,自動搜索所有事件和事件處理器,並自動設置好它們之間的映射,所以我們就不需要在手動做這個事情了。
創建Notification
在我們之前的代碼中,我們定義了一個購物車提交事件,它繼承自事件基類EventBase
。
public class ShoppingCartSubmittedEvent : EventBase
{
public ShoppingCartSubmittedEvent()
{
Items = new List<ShoppingCartSubmittedItem>();
}
public List<ShoppingCartSubmittedItem> Items { get; set; }
}
現在改用MediatR之后,我們需要修改當前事件的定義,讓它實現INotification
接口。
public class ShoppingCartSubmittedEvent : INotification
{
public ShoppingCartSubmittedEvent()
{
Items = new List<ShoppingCartSubmittedItem>();
}
public List<ShoppingCartSubmittedItem> Items { get; set; }
}
NotificationHandler
完成事件定義部分的修改之后,我們還需要重構事件處理器的代碼。
在之前的代碼中,針對購物車提交事件,我們定義了兩個處理器,一個是創建訂單處理器CreateOrderHandler
,一個是發送郵件處理器ConfirmEmailSentHandler
。
現在我們來使用INotificationHandler
接口來改造之前定義好的兩個處理器。
public class CreateOrderHandler : INotificationHandler<ShoppingCartSubmittedEvent>
{
private IOrderManager _orderManager = null;
public CreateOrderHandler(IOrderManager orderManager)
{
_orderManager = orderManager;
}
public Task Handle(ShoppingCartSubmittedEvent notification, CancellationToken cancellationToken)
{
_orderManager.CreateNewOrder(new Models.DTOs.CreateOrderDTO
{
Items = notification.Items.Select(p => new Models.DTOs.NewOrderItemDTO
{
ItemId = p.ItemId,
Name = p.Name,
Price = p.Price
}).ToList()
});
return Task.CompletedTask;
}
}
public class ConfirmEmailSentHandler : INotificationHandler<ShoppingCartSubmittedEvent>
{
public Task Handle(ShoppingCartSubmittedEvent notification, CancellationToken cancellationToken)
{
Console.WriteLine("Confirm Email Sent.");
return Task.CompletedTask;
}
}
代碼解釋:
INotificationHandler
是一個泛型接口,接口中定義的泛型類需要實現INotification
接口- 當處理器實現
INotificationHandler
接口時,就需要實現一個Handle
方法, 在該方法中,我們可以編寫具體的業務代碼- 從方法的返回值Task, 你可以了解到這個方法是沒有返回值的,並且可以使用async/await變為一個異步的版本。
發布事件
在之前的代碼中,當購物車提交成功之后,我們會在OrderManager
類中,使用EventContainer
發布事件。當我們使用MediatR之后,這部分代碼稍有改動, 我們需要使用IMediator
接口對象的Publish
方法來發布事件。
public void SubmitShoppingCart(string shoppingCartId)
{
var shoppingCart = _unitOfWork.ShoppingCartRepository.GetShoppingCart(shoppingCartId);
_unitOfWork.ShoppingCartRepository.SubmitShoppingCart(shoppingCartId);
_mediator.Publish(new ShoppingCartSubmittedEvent()
{
Items = shoppingCart.Items.Select(p => new ShoppingCartSubmittedItem
{
ItemId = p.ItemId,
Name = p.Name,
Price = p.Price
}).ToList()
});
_unitOfWork.Save();
}
最終效果
至此,所有代碼就都完成了,我們可以按照上一篇的操作步驟,再測試一次。
當執行購物車提交操作的時候,訂單創建和郵件發送處理器都正確觸發了。
總結
MediatR是一個基於.NET的中介者模式實現,它雖然只支持進程內的消息傳遞,但是卻可以簡化事件發布/訂閱代碼,幫助實現業務邏輯代碼的解耦,你可以自己試一試。