事件是在软件开发过程中经常用到的一种思路和形式,事件常常是和观察者模式、订阅发布这样的词汇联系在一起。在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();
以上示例比较简单,只是为了说明问题,更多深度用法可以查看官方文档并在实际工作中灵活运用。