详解ABP框架中领域层的领域事件Domain events
在c#中,一个类可以定义其专属的事件并且其它类可以注册该事件并监听,当事件被触发时可以获得事件通知。这对于对于桌面应用程序或独立的windows service来说非常有用。但是, 对于web应用程序来说会有点问题,因为对象是根据请求(request)被创建并且它们的生命周期都很短暂。我们很难注册其它类别的事件。同样地,直接注册其它类别的事件也造成了类之间的耦合性。
在应用系统中,领域事件被用于解耦并且重用(re-use)商业逻辑。
事件总线
事件总线为一个单体(singleton)的对象,它由所有其它类所共享,可通过它触发和处理事件。要使用这个事件总线,你需要引用它。你可以用两种方式来实现:
获取默认实例( getting the default instance)
你可以直接使用eventbus.default。它是全局事件总线并且可以如下方式使用:
eventbus.default.trigger(...); //触发事件
注入ieventbus事件接口(injecting ieventbus)
除了直接使用eventbus.default外,你还可以使用依赖注入(di)的方式来取得ieventbus的参考。这利于进行单元测试。在这里,我们使用属性注入的范式:
public class taskappservice : applicaservice { public ieventbus eventbus { get; set; } public taskappservice() { eventbus = nulleventbus.instance; } }
注入事件总线,采用属性注入比建构子注入更适合。事件是由类所描述并且该事件对象继承自eventdata。假设我们想要触发某个事件于某个任务完成后:
public class taskcompletedeventdata : eventdata { public int taskid { get; set; } }
这个类所包含的属性都是类在处理事件时所需要的。eventdata类定义了eventsource(那个对象触发了这个事件)和eventtime(何时触发)属性。
定义事件
abp定义abphandledexceptiondata事件并且在异常发生的时候自动地触发这个事件。这在你想要取得更多关于异常的信息时特别有用(即便abp已自动地纪录所有的异常)。你可以注册这个事件并且设定它的触发时机是在异常发生的时候。
abp也提供在实体变更方面许多的通用事件数据类: entitycreatedeventdata, entityupdatedeventdata和entitydeletedeventdata。它们被定义在abp.events.bus.entitis命名空间中。当某个实体新增/更新/删除后,这些事件会由abp自动地触发。如果你有一个person实体,可以注册到entitycreatedeventdata,事件会在新的person实体创建且插入到数据库后被触发。这些事件也支持继承。如果student类继承自person类,并且你注册到entitycreatedeventdata中,接着你将会在person或student新增后收到触发。
触发事件
触发事件的范例如下:
public class taskappservice : applicationservice { public ieventbus eventbus { get; set; } public taskappservice() { eventbus = nulleventbus.instance; } public void completetask(completetaskinput input) { //todo: 已完成数据库上的任务 eventbus.trigger(new taskcompletedeventdata { taskid = 42 } ); } }
这里有一些触发方法的重载:
eventbus.trigger<taskcompletedeventdata>(new taskcompletedeventdata { taskid = 42}); eventbus.trigger(this, new taskcompletedeventdata { taskid = 42 }); eventbus.trigger(typeof(taskcompletedeventdata), this, new taskcompletedeventdata { taskid = 42});
事件处理
要进行事件的处理,你应该要实现ieventhandler接口如下所示:
public class activitywriter : ieventhandler<taskcompletedeventdata>, itransientdependency { public void handleevent(taskcompletedeventdata eventdata) { writeactivity("a task is completed by id = " + eventdata.taskid); } }
eventbus已集成到依赖注入系统中。就如同我们在上例中实现itransientdependency那样,当taskcompleted事件触发,它会创建一个新的activitywriter类的实体并且调用它的handleevent方法,并接着释放它。详情请见依赖注入(di)一文。
1.基础事件的处理(handling base events)
eventbus支持事件的继承。举例来说,你可以创建taskeventdata以及两个继承类:taskcompletedeventdata和taskcreatedeventdata:
public class taskeventdata : eventdata { public task task { get; set; } } public class taskcreatedeventdata : taskeventdata { public user creatoruser { get; set; } } public class taskcompletedeventdata : taskeventdata { public user completoruser { get; set; } }
然而,你可以实现ieventhandler来处理这两个事件:
public class activitywriter : ieventhandler<taskeventdata>, itransientdependency { public void handleevent(taskeventdata eventdata) { if(eventdata is taskcreatedeventdata) { ... }else{ ... } } }
当然,你也可以实现ieventhandler来处理所有的事件,如果你真的想要这样做的话(译者注:作者不太建议这种方式)。
2.处理多个事件(handling multiple events)
在单个处理器(handler)中我们可以可以处理多个事件。此时,你应该针对不同事件实现ieventhandler。范例如下:
public class activitywriter : ieventhandler<taskcompletedeventdata>, ieventhandler<taskcreatedeventdata>, itransientdependency { public void handleevent(taskcompletedeventdata eventdata) { //todo: 处理事件 } public void handleevent(taskcreatedeventdata eventdata) { //todo: 处理事件 } }
注册处理器
我们必需注册处理器(handler)到事件总线中来处理事件。
1.自动型automatically
abp扫描所有实现ieventhandler接口的类,并且自动注册它们到事件总线中。当事件发生, 它通过依赖注入(di)来取得处理器(handler)的引用对象并且在事件处理完毕之后将其释放。这是比较建议的事件总线使用方式于abp中。
2.手动型(manually)
也可以通过手动注册事件的方式,但是会有些问题。在web应用程序中,事件的注册应该要在应用程序启动的时候。当一个web请求(request)抵达时进行事件的注册,并且反复这个行为。这可能会导致你的应用程序发生一些问题,因为注册的类可以被调用多次。同样需要注意的是,手动注册无法与依赖注入系统一起使用。
abp提供了多个事件总线注册方法的重载(overload)。最简单的一个重载方法是等待委派(delegate)或lambda。
eventbus.register<taskcompletedeventdata>(eventdata => { writeactivity("a task is completed by id = " + eventdata.taskid); });
因此,事件:task completed会发生,而这个lambda方法会被调用。第二个重载方法等待的是一个对象,该对象实现了ieventhandler:
eventbus.register<taskcompletedeventdata>(new activitywriter());
相同的例子,如果activitywriter因事件而被调用。这个方法也有一个非泛型的重载。另一个重载接受两个泛化的参数:
eventbus.register<taskcompletedeventdata, activitywriter>();
此时,事件总线创建一个新的activitywriter于每个事件。当它释放的时候,它会调用activitywriter.dispose方法。
最后,你可以注册一个事件处理器工厂(event handler factory)来负责创建处理器。处理器工厂有两个方法: gethandler和releasehandler,范例如下:
public class activitywriterfactory : ieventhandlerfactory { public ieventhandler gethandler() { return new activitywriter(); } public void releasehandler(ieventhandler handler) { //todo: 释放activitywriter实体(处理器) } }
abp也提供了特殊的工厂类,iochandlerfactory,通过依赖注入系统,iochandlerfactory可以用来创建或者释放(dispose)处理器。abp可以自动化注册iochandlerfactory。因此,如果你想要使用依赖注入系统,请直接使用自动化注册的方式。
取消注册事件
当你手动注册事件总线,你或许想要在之后取消注册。最简单的取消事件注册的方式即为registration.dispose()。举例如下:
//注册一个事件 var registration = eventbus.register<taskcompletedeventdata>(eventdata => writeactivity("a task is completed by id = " + eventdata.taskid)); //取消注册一个事件 registration.dispose();
当然,取消注册可以在任何地方任何时候进行。保存(keep)好注册的对象并且在你想要取消注册的时候释放(dispose)掉它。所有注册方法的重载(overload)都会返回一个可释放(disposable)的对象来取消事件的注册。
事件总线也提供取消注册方法。使用范例:
//创建一个处理器 var handler = new activitywriter(); //注册一个事件 eventbus.register<taskcompletedeventdata>(handler); //取消这个事件的注册 eventbus.unregister<taskcompletedeventdata>(handler);
它也提供重载的方法给取消注册的委派和工厂。取消注册处理器对象必须与之前注册的对象是同一个。
最后,eventbus提供一个unregisterall()方法来取消某个事件所有处理器的注册,而unregisterall()方法则是所有事件的所有处理器。