💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 3.6 ABP领域层 - 领域事件 在C#中,一个类可以定义其专属的事件并且其它类可以注册该事件并监听,当事件被触发时可以获得事件通知。这对于对于桌面应用程序或独立的Windows Service来说非常有用。但是, 对于Web应用程序来说会有点问题,因为对象是根据请求(request)被创建并且它们的生命周期都很短暂。我们很难注册其它类别的事件。同样地,直接注册其它类别的事件也造成了类之间的耦合性。 在应用系统中,领域事件可以被用于解耦业务逻辑以及对领域的重要更改做出反应。 ### 3.6.1 事件总线 事件总线为一个单体(singleton)的对象,它由所有其它类所共享,可通过它触发和处理事件。要使用这个事件总线,你需要引用它。你可以用两种方式来实现: #### 1. 注入IEventBus事件接口 你可以使用依赖注入来获取对 **IEventBus** 的引用。这里使用的是属性注入的方式: ```csharp public class TaskAppService : ApplicationService { public IEventBus EventBus { get; set; } public TaskAppService() { EventBus = NullEventBus.Instance; } } ``` 对于事件总线,更适合的方式是属性注入而不是构造函数注入。因此,你的类可以没有事件总线也能够正常的工作。**NullEventBus** 实现了空对象模式。当你调用它的方法,它不会做任何事情。 #### 2. 获取默认实例 如果你不能注入它,你可以直接使用 **EventBus.Default**。它是全局事件总线并且可以以如下方式使用: ```csharp EventBus.Default.Trigger(...); //触发事件 ``` 无论在什么地方,建议你尽可能的别直接使用EventBus.Default,因为那样使单元测试会很困难。 ### 3.6.2 定义事件 触发事件之前,首先你应该定义该事件。事件是派生自 **EventData** 的类。假设我们想要触发一个事件,当某个任务完成的时候: ```csharp public class TaskCompletedEventData : EventData { public int TaskId { get; set; } } ``` 这个类包含了处理这个事件所需要包含的属性。**EventData** 定义了 **EventSource** (那个对象触发了该事件) 以及 **EventTime** (什么时候触发的) 属性。 #### 预定义事件 ##### 处理异常 ABP定义AbpHandledExceptionData事件,并且在异常发生的时候自动地触发这个事件。这在你想要取得更多关于异常的信息时特别有用(即便ABP已自动地纪录所有的异常)。你可以注册这个事件来发出通知当某个异常发生的时候。 ##### 实体变更 对于实体变更,ABP也定义了一下事件的泛型版本: **EntityCreatingEventData\<TEntity\>, EntityCreatedEventData\<TEntity\>, EntityUpdatingEventData\<TEntity\>, EntityUpdatedEventData\<TEntity\>, EntityDeletingEventData\<TEntity\> and EntityDeletedEventData\<TEntity\>**。还有,对于泛型事件:**EntityChangingEventData\<TEntity\> 和 EntityChangedEventData\<TEntity\>** 是在某个实体发生插入,更新或者删除的时候出发。 **ing** 事件是指(例如:EntityUpdating) 该类事件被触发是在保存实体更改到数据库之前。所以你可以在这些事件里面抛出某个异常来回滚工作单元来阻止当前的更改操作。**ed** 事件是指(例如:EntityUpdated) 该类事件被触发是在保存实体更改到数据库之后,这样就不可能有机会回滚工作单元。 实体更改事件被定义在 **Abp.Events.Bus.Entities** 命名空间,并且在某个实体被插入,更新或者删除的时候,ABP可以自动的触发它们。如果你有一个Person实体,可以注册 **EntityCreatedEventData\<Person\>** 事件来产生通知,当一个新的的Person实体被创建且插入到数据库的时候,ABP就会自动的触发该事件。这些事件也支持继承。如果Student类继承自Person类,并且你注册了 **EntityCreatedEventData\<Person\>** 事件,接着你将会在Person或Student实体被插入后后收到触发。 ### 3.6.3 触发事件 触发事件的范例如下: ```csharp 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 } ); } } ``` 这里有一些触发方法的重载: ```csharp EventBus.Trigger<TaskcompletedEventData>(new TaskCompletedEventData { TaskId = 42}); EventBus.Trigger(this, new TaskCompletedEventData { TaskId = 42 }); EventBus.Trigger(typeof(TaskCompletedEventData), this, new TaskCompletedEventData { TaskId = 42}); ``` 其他触发事件的方式是我们可以使用聚合根类的领域事件集合,详细请参考文档:[实体](225833)。 ### 3.6.4 事件处理 要进行事件的处理,你应该要实现IEventHandler\<T\>接口如下所示: ```csharp 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: ```csharp 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\<TaskEventData\>来处理这两个事件: ```csharp public class ActivityWriter : IEventHandler<TaskEventData>, ITransientDependency { public void HandleEvent(TaskEventData eventData) { if(eventData is TaskCreatedEventData) { ... }else{ ... } } } ``` 当然,你也可以实现IEventHandler\<EventData\>来处理所有的事件,如果你真的想要这样做的话(译者注:作者不太建议这种方式)。 #### 2. 处理多个事件(Handling multiple events) 在单个处理器(handler)中我们可以处理多个事件。此时,你应该针对不同事件实现IEventHandler\<T\>。范例如下: ``` csharp public class ActivityWriter : IEventHandler<TaskCompletedEventData>, IEventHandler<TaskCreatedEventData>, ITransientDependency { public void HandleEvent(TaskCompletedEventData eventData) { //TODO: 处理事件 } public void HandleEvent(TaskCreatedEventData eventData) { //TODO: 处理事件 } } ``` #### 3. 处理异常 事件总线触发所有异常处理器,即使抛出异常是这些中的某个或者某些。如果你仅想抛出这些异常中的某一个,那么你可以直接通过该触发器方法抛出异常。如果超过一个的处理器抛出异常,那么事件总线会对这些异常抛出一个单独的 **AggregateException**。 ### 3.6.5 注册处理器 我们必需注册处理器(handler)到事件总线中来处理事件。 #### 1. 自动型Automatically ABP扫描所有实现IEventHandler接口的类,并且自动注册它们到事件总线中。当事件发生, 它通过依赖注入(DI)来取得处理器(handler)的引用对象并且在事件处理完毕之后将其释放。这是比较建议的事件总线使用方式于ABP中。 #### 2. 手动型(Manually) 也可以通过手动注册事件的方式,但是会有些问题。在Web应用程序中,事件的注册应该要在应用程序启动的时候。当一个Web请求(request)抵达时进行事件的注册,并且反复这个行为。这可能会导致你的应用程序发生一些问题,因为注册的类可以被调用多次。同样需要注意的是,手动注册无法与依赖注入系统一起使用。 ABP提供了多个事件总线注册方法的重载(overload)。最简单的一个重载方法是等待委派(delegate)或Lambda。 ```csharp EventBus.Register<TaskCompletedEventData>(eventData => { WriteActivity("A task is completed by id = " + eventData.TaskId); }); ``` 因此,事件:task completed会发生,而这个Lambda方法会被调用。第二个重载方法等待的是一个对象,该对象实现了IEventHandler\<T\>: ```csharp Eventbus.Register<TaskCompletedEventData>(new ActivityWriter()); ``` 相同的例子,如果ActivityWriter因事件而被调用。这个方法也有一个非泛型的重载。另一个重载接受两个泛化的参数: ```csharp EventBus.Register<TaskCompletedEventData, ActivityWriter>(); ``` 此时,事件总线创建一个新的ActivityWriter于每个事件。当它释放的时候,它会调用ActivityWriter.Dispose方法。 最后,你可以注册一个事件处理器工厂(event handler factory)来负责创建处理器。处理器工厂有两个方法: GetHandler和ReleaseHandler,范例如下: ```csharp public class ActivityWriterFactory : IEventHandlerFactory { public IEventHandler GetHandler() { return new ActivityWriter(); } public void ReleaseHandler(IEventHandler handler) { //TODO: 释放ActivityWriter实体(处理器) } } ``` ABP也提供了特殊的工厂类,IocHandlerFactory,通过依赖注入系统,IocHandlerFactory可以用来创建或者释放(dispose)处理器。ABP可以自动化注册IocHandlerFactory。因此,如果你想要使用依赖注入系统,请直接使用自动化注册的方式。 ### 3.6.6 取消注册事件 当你手动注册事件总线,你或许想要在之后取消注册。最简单的取消事件注册的方式即为registration.Dispose()。举例如下: ```csharp //注册一个事件 Var registration = EventBus.Register<TaskCompletedEventData>(eventData => WriteActivity("A task is completed by id = " + eventData.TaskId)); //取消注册一个事件 registration.Dispose(); ``` 当然,取消注册可以在任何地方任何时候进行。保存(keep)好注册的对象并且在你想要取消注册的时候释放(dispose)掉它。所有注册方法的重载(overload)都会返回一个可释放(disposable)的对象来取消事件的注册。 事件总线也提供取消注册方法。使用范例: ```csharp //创建一个处理器 var handler = new ActivityWriter(); //注册一个事件 EventBus.Register<TaskCompletedEventData>(handler); //取消这个事件的注册 EventBus.Unregister<TaskCompletedEventData>(handler); ``` 它也提供重载的方法给取消注册的委派和工厂。取消注册处理器对象必须与之前注册的对象是同一个。 最后,EventBus提供一个UnregisterAll\<T\>()方法来取消某个事件所有处理器的注册,而UnregisterAll()方法则是所有事件的所有处理器。 > (3.1由Carl翻译,3.2-3.5由台湾-小张翻译)