事件是在軟件開發過程中經常用到的一種思路和形式,事件常常是和觀察者模式、訂閱發佈這樣的詞彙聯繫在一起。在ABP框架中同樣也少不了事件,也就是領域事件。
1.領域事件的使用範圍
在具體業務中常常會有這樣的需求,以前面的貨品管理功能爲例,對於某種特定類型的貨品,我們希望在貨品庫存數量低於某個特定值的時候得到提醒,以便於進行採購補貨或其他操作,就是常說的庫存預警功能。解決這個問題最簡單的思路就是寫一個方法循環查詢庫存數量,當數量低於特定值時執行預先設定的操作。但這樣有兩個比較明顯的問題:一是循環的讀取查詢庫存耗時耗力,浪費大量資源;二是庫存預警功能和貨品管理功能的代碼關聯性太強,一旦業務需求發生調整,改起來真是“牽一髮而動全身”,耦合性太強。在這樣的背景下,事件就有了用武之地。
簡單來說,一個類可以定義其專屬的事件並且其它類可以註冊該事件並監聽,當事件被觸發時可以獲得事件通知。當我們需要解耦業務邏輯以及對領域對象的變化做出反應時,就需要用到領域服務。
2.定義並註冊領域事件
在ABP框架中,領域事件相關的類位於Abp.Events.Bus命名空間下。事件是派生自 EventData 的類。在領域層AbpDemo.Core中定義一個貨品數量變更的事件。
/// <summary>
/// 貨品庫存變更事件
/// </summary>
public class GoodsNumChangedEventData:EventData
{
/// <summary>
/// 貨品標識
/// </summary>
public string Id { get; set; }
/// <summary>
/// 貨品名稱
/// </summary>
public string GoodsName { get; set; }
/// <summary>
/// 當前數量
/// </summary>
public int GoodsNum { get; set; }
/// <summary>
/// 數量下限
/// </summary>
public int MinNum { get; set; }
}
在應用層AbpDemo.Application中使用依賴注入來獲取對 IEventBus 的引用,實現對領域事件的註冊,之後可以在具體的業務邏輯代碼中觸發事件。在貨品管理模塊中,以出庫操作爲例,當出庫完成後,如果貨品數量低於預先設定的下限,則觸發事件。
/// <summary>
/// 貨品管理-應用服務
/// </summary>
public class GoodsAppService: AbpDemoAppServiceBase<Goods,DetailGoodsDto,string,CreateGoodsDto,UpdateGoodsDto,PagedGoodsDto>,IGoodsAppService
{
private readonly IGoodsRecordManager _goodsRecordManager;//出入庫記錄領域服務
private readonly IGoodsManager _goodsManager;//貨品管理領域服務
public IEventBus EventBus { get; set; }//事件總線
private const int MinNum = 50;//貨品數量下限
public GoodsAppService(IRepository<Goods,string> repository,IGoodsRecordManager goodsRecordManager,IGoodsManager goodsManager):base(repository)
{
_goodsRecordManager = goodsRecordManager;
_goodsManager = goodsManager;
EventBus = NullEventBus.Instance;
}
/// <summary>
/// 出庫
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<DetailGoodsDto> Out(InOutGoodsDto input)
{
Goods entity = Repository.FirstOrDefault(input.Id);
if (entity != null)
{
entity.GoodsNum = entity.GoodsNum - input.GoodsNum;
}
GoodsRecord record = input.MapTo<GoodsRecord>();
record.OperateType = GoodsOperateType.Out;
string recordId = await _goodsRecordManager.OutRecord(record);
entity = await Repository.UpdateAsync(entity);
if (entity.GoodsNum<=MinNum)//貨品數量低於下限時觸發事件
{
EventBus.Trigger(new GoodsNumChangedEventData
{
Id = entity.Id,
GoodsName = entity.GoodsName,
GoodsNum = entity.GoodsNum,
MinNum=MinNum
}) ;
}
DetailGoodsDto result = entity.MapTo<DetailGoodsDto>();
return await Task.FromResult(result);
}
}
3.領域事件的訂閱和處理
在事件觸發後,事件的訂閱者就可以收到通知,進而對事件進行處理。在ABP框架中,要對領域事件進行處理,就要實現IEventHandler接口。
public class GoodsChangedManager : IEventHandler<GoodsNumChangedEventData>, ITransientDependency
{
public void HandleEvent(GoodsNumChangedEventData eventData)
{
string message = string.Format("貨品{0}當前庫存爲{1},低於最低允許庫存{2},請及時採購補充!", eventData.GoodsName, eventData.GoodsNum, eventData.MinNum);
/*
* To do
* 後續處理
* */
}
}
在上面的代碼中,當貨品數量低於下限時觸發事件,在HandedEvent方法中就可以進行具體操作了,比如說通知其他領域對象或發送消息到外部。
在ABP框架中,項目啓動時所有實現IEventHandler接口的類都會自動註冊到事件總線中。當事件發生, 通過依賴注入(DI)來取得處理器(handler)的引用對象並且在事件處理完畢之後將其釋放。除了自動註冊事件外,ABP框架還支持手動註冊和卸載事件。
//註冊事件
var goodsChangedEvent = EventBus.Register<GoodsNumChangedEventData>(data =>
{
/*
* To do
**/
});
//取消註冊事件
goodsChangedEvent.Dispose();
以上示例比較簡單,只是爲了說明問題,更多深度用法可以查看官方文檔並在實際工作中靈活運用。