欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解ABP框架中领域层的领域事件Domain events

程序员文章站 2023-12-04 16:08:46
在c#中,一个类可以定义其专属的事件并且其它类可以注册该事件并监听,当事件被触发时可以获得事件通知。这对于对于桌面应用程序或独立的windows service来说非常有用...

在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()方法则是所有事件的所有处理器。

相关标签: ABP