From 533d99c51ee58866ab73087a0962685f15bd2a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 15:31:08 +0300 Subject: [PATCH 1/8] Complete the event bus nav items --- docs/en/docs-nav.json | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index c49b053ce0..6c3b3a94de 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -153,16 +153,23 @@ ] }, { - "text": "Events", + "text": "Event Bus", "items": [ { - "text": "Event Bus (local)" + "text": "Overall", + "path": "Event-Bus.md" + }, + { + "text": "Local Event Bus", + "path": "Local-Event-Bus.md" }, { "text": "Distributed Event Bus", + "path": "Distributed-Event-Bus.md", "items": [ { - "text": "RabbitMQ Integration" + "text": "RabbitMQ Integration", + "path": "Distributed-Event-Bus-RabbitMQ-Integration.md" } ] } From b614033568165204a4580d633d219b26f0d98019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 15:52:58 +0300 Subject: [PATCH 2/8] Event Bus Overall document --- docs/en/Event-Bus.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/en/Event-Bus.md b/docs/en/Event-Bus.md index cf2b57c018..a0e6859465 100644 --- a/docs/en/Event-Bus.md +++ b/docs/en/Event-Bus.md @@ -1,3 +1,10 @@ # Event Bus -TODO \ No newline at end of file +An event bus is a mediator that transfers a message from a sender to a receiver. In this way, it provides a loosely coupled communication way between objects, services and applications. + +## Event Bus Types + +ABP Framework provides two type of event buses; + +* **[Local Event Bus](Local-Event-Bus.md)** is suitable for in-process messaging. +* **[Distributed Event Bus](Distributed-Event-Bus.md)** is suitable for inter-process messaging, like microservices publishing and subscribing to distributed events. \ No newline at end of file From 9b9984c5d6376c0eb1e529a747418387d85dc3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 16:42:37 +0300 Subject: [PATCH 3/8] Update Local-Event-Bus.md --- docs/en/Local-Event-Bus.md | 164 +++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/docs/en/Local-Event-Bus.md b/docs/en/Local-Event-Bus.md index 859ec8d28e..62b12c8457 100644 --- a/docs/en/Local-Event-Bus.md +++ b/docs/en/Local-Event-Bus.md @@ -1,3 +1,167 @@ # Local Event Bus +The Local Event Bus allows services to publish and subscribe to **in-process events**. That means it is suitable if two services (publisher and subscriber) are running in the same process. + +## Publishing Events + +There are two ways of publishing local events explained in the following sections. + +### ILocalEventBus + +`ILocalEventBus` can be [injected](Dependency-Injection.md) and used to publish a local event. + +**Example: Publish an event when the stock count of a product changes** + +````csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Local; + +namespace AbpDemo +{ + public class MyService : ITransientDependency + { + private readonly ILocalEventBus _localEventBus; + + public MyService(ILocalEventBus localEventBus) + { + _localEventBus = localEventBus; + } + + public virtual async Task ChangeStockCountAsync(Guid productId, int newCount) + { + //TODO: IMPLEMENT YOUR LOGIC... + + //PUBLISH THE EVENT + await _localEventBus.PublishAsync( + new StockCountChangedEvent + { + ProductId = productId, + NewCount = newCount + } + ); + } + } +} +```` + +`PublishAsync` method gets a single parameter: the event object, which is responsible to hold the data related to the event. It is a simple plain class: + +````csharp +using System; + +namespace AbpDemo +{ + public class StockCountChangedEvent + { + public Guid ProductId { get; set; } + + public int NewCount { get; set; } + } +} +```` + +Even if you don't need to transfer any data, you need to create a class (which is an empty class in this case). + +### Inside Entity / Aggregate Root Classes + +[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish events inside entity / aggregate root classes. + +**Example: Publish an event inside an aggregate root method** + +````csharp +using System; +using Volo.Abp.Domain.Entities; + +namespace AbpDemo +{ + public class Product : AggregateRoot + { + public string Name { get; set; } + + public int StockCount { get; private set; } + + private Product() { } + + public Product(Guid id, string name) + : base(id) + { + Name = name; + } + + public void ChangeStockCount(int newCount) + { + StockCount = newCount; + + //ADD an EVENT TO BE PUBLISHED + AddLocalEvent( + new StockCountChangedEvent + { + ProductId = Id, + NewCount = newCount + } + ); + } + } +} +```` + +`AggregateRoot` class defines the `AddLocalEvent` to add a new local event, that is published when the aggregate root object is saved (created or updated) into the database. + +> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` which guarantees publishing the event. + +#### IGeneratesDomainEvents Interface + +Actually, adding local events are not unique to the `AggregateRoot` class. You can implement `IGeneratesDomainEvents` for any entity class. But, `AggregateRoot` implements it by default and makes it easy for you. + +> It is not suggested to implement this interface for entities those are not aggregate roots, since it may not work for some database providers for such entities. It works for EF Core, but not works for MongoDB for example. + +#### How It Was Implemented? + +Calling the `AddLocalEvent` doesn't immediately publish the event. The event is published when you save changes to the database; + +* For EF Core, it is published on `DbContext.SaveChanges`. +* For MongoDB, it is published when you call repository's `InsertAsync` or `UpdateAsync` methods (since MongoDB has no such a change tracking system). + +## Subscribing to Events + +A service can implement the `ILocalEventHandler` to handle the event. + +**Example: Handle the `StockCountChangedEvent` defined above** + +````csharp +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; + +namespace AbpDemo +{ + public class MyHandler + : ILocalEventHandler, + ITransientDependency + { + public async Task HandleEventAsync(StockCountChangedEvent eventData) + { + //TODO: your code that does somthing on the event + } + } +} +```` + +That's all. `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEvent` occurs. You can inject any service and perform any required logic here. + +There can be zero or more handlers subscribed to the same event. + +> The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. + +## Transaction & Exception Behavior + +When an event published, subscribed event handlers are immediately executed. So; + +* If a handler **throws an exception**, it effects the code that published the event. That means it gets the exception on the `PublishAsync` call. So, **use try-catch yourself** in the event handler if you want to hide the error. +* If the event publishing code is being executed inside a [Unit Of Work](Unit-Of-Work.md) scope, the event handlers also covered by the unit of work. That means if your UOW is transactional and a handler throws an exception, the transaction is rolled back. + +## Pre-Built Events + TODO \ No newline at end of file From 47eb121d00f1b6df0af753205fb2f93140f0efa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 17:28:27 +0300 Subject: [PATCH 4/8] Complete the local event bus document. --- docs/en/Local-Event-Bus.md | 66 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/docs/en/Local-Event-Bus.md b/docs/en/Local-Event-Bus.md index 62b12c8457..3aa9fd7d07 100644 --- a/docs/en/Local-Event-Bus.md +++ b/docs/en/Local-Event-Bus.md @@ -122,7 +122,7 @@ Actually, adding local events are not unique to the `AggregateRoot` class. You c Calling the `AddLocalEvent` doesn't immediately publish the event. The event is published when you save changes to the database; * For EF Core, it is published on `DbContext.SaveChanges`. -* For MongoDB, it is published when you call repository's `InsertAsync` or `UpdateAsync` methods (since MongoDB has no such a change tracking system). +* For MongoDB, it is published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system). ## Subscribing to Events @@ -151,7 +151,8 @@ namespace AbpDemo That's all. `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEvent` occurs. You can inject any service and perform any required logic here. -There can be zero or more handlers subscribed to the same event. +* **Zero or more handlers** can subscribe to the same event. +* A single event handler class can **subscribe to multiple events** but implementing the `ILocalEventHandler` interface for each event type. > The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. @@ -164,4 +165,63 @@ When an event published, subscribed event handlers are immediately executed. So; ## Pre-Built Events -TODO \ No newline at end of file +It is very common to **publish events on entity create, update and delete** operations. ABP Framework **automatically** publish these events for all entities. You can just subscribe to the related event. + +**Example: Subscribe to an event that published when a user was created** + +````csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.EventBus; + +namespace AbpDemo +{ + public class MyHandler + : ILocalEventHandler>, + ITransientDependency + { + public async Task HandleEventAsync( + EntityCreatedEventData eventData) + { + var userName = eventData.Entity.UserName; + var email = eventData.Entity.Email; + //... + } + } +} +```` + +This class subscribes to the `EntityCreatedEventData`, which is published just after a user was created. You may want to send a "Welcome" email to the new user. + +There are two types of these events: events with past tense and events with continuous tense. + +### Events with Past Tense + +Events with past tense are published when the related unit of work completed and the entity change successfully saved to the database. If you throw an exception on these event handlers, it **can not rollback** the transaction since it was already committed. + +The event types are; + +* `EntityCreatedEventData` is published just after an entity was successfully created. +* `EntityUpdatedEventData` is published just after an entity was successfully updated. +* `EntityDeletedEventData` is published just after an entity was successfully deleted. +* `EntityChangedEventData` is published just after an entity was successfully created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events. + +### Events with Continuous Tense + +Events with continuous tense are published before completing the transaction (if database transaction is supported by the database provider being used). If you throw an exception on these event handlers, it **can rollback** the transaction since it is not completed yet and the change is not saved to the database. + +The event types are; + +* `EntityCreatingEventData` is published just before saving a new entity to the database. +* `EntityUpdatingEventData` is published just before an existing entity is being updated. +* `EntityDeletingEventData` is published just before an entity is being deleted. +* `EntityChangingEventData` is published just before an entity is being created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events. + +#### How It Was Implemented? + +Pre-build events are published when you save changes to the database; + +* For EF Core, they are published on `DbContext.SaveChanges`. +* For MongoDB, they are published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system). \ No newline at end of file From 7f1c5e4ed0e6bac581b44a8862e02643b1249f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 17:30:52 +0300 Subject: [PATCH 5/8] Update Local-Event-Bus.md --- docs/en/Local-Event-Bus.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Local-Event-Bus.md b/docs/en/Local-Event-Bus.md index 3aa9fd7d07..4452149f56 100644 --- a/docs/en/Local-Event-Bus.md +++ b/docs/en/Local-Event-Bus.md @@ -109,7 +109,7 @@ namespace AbpDemo `AggregateRoot` class defines the `AddLocalEvent` to add a new local event, that is published when the aggregate root object is saved (created or updated) into the database. -> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` which guarantees publishing the event. +> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` method which guarantees publishing the event. #### IGeneratesDomainEvents Interface From fbd888a787520555d3192ea2cb8cf6b17e3666e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 18:01:18 +0300 Subject: [PATCH 6/8] Update Distributed-Event-Bus.md --- docs/en/Distributed-Event-Bus.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/en/Distributed-Event-Bus.md b/docs/en/Distributed-Event-Bus.md index 03beb814d2..2120ac45a3 100644 --- a/docs/en/Distributed-Event-Bus.md +++ b/docs/en/Distributed-Event-Bus.md @@ -1,3 +1,14 @@ # Distributed Event Bus +Distributed Event bus system allows to publish and subscribe to events that can be transferred across application/service boundaries. You can use the distributed event bus to asynchronously send and receive message between microservices or applications. + +## Providers + +Distributed event bus system provides an abstraction that can be implemented by any vendor/provider. There are two providers implemented out of the box: + +* `LocalDistributedEventBus` is the default implementation that implements the distributed event bus to work as in-process. Yes! The **default implementation works just like the [local event bus](Local-Event-Bus.md)**, if you don't configure a real distributed provider. +* `RabbitMqDistributedEventBus` implements the distributed event bus with the [RabbitMQ](https://www.rabbitmq.com/). See the [RabbitMQ integration document](Distributed-Event-Bus-RabbitMQ-Integration.md) to learn how to configure it. + +## Publishing Events + TODO \ No newline at end of file From 33f3412f5dd0d7f703a49419747ae6f19cd0fb16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 18 Jun 2020 11:22:16 +0300 Subject: [PATCH 7/8] Initially documented the distributed event bus --- docs/en/Distributed-Event-Bus.md | 175 ++++++++++++++++++++++++++++++- docs/en/Local-Event-Bus.md | 8 +- 2 files changed, 177 insertions(+), 6 deletions(-) diff --git a/docs/en/Distributed-Event-Bus.md b/docs/en/Distributed-Event-Bus.md index 2120ac45a3..db6dd95cc3 100644 --- a/docs/en/Distributed-Event-Bus.md +++ b/docs/en/Distributed-Event-Bus.md @@ -1,14 +1,185 @@ # Distributed Event Bus -Distributed Event bus system allows to publish and subscribe to events that can be transferred across application/service boundaries. You can use the distributed event bus to asynchronously send and receive message between microservices or applications. +Distributed Event bus system allows to **publish** and **subscribe** to events that can be **transferred across application/service boundaries**. You can use the distributed event bus to asynchronously send and receive messages between **microservices** or **applications**. ## Providers -Distributed event bus system provides an abstraction that can be implemented by any vendor/provider. There are two providers implemented out of the box: +Distributed event bus system provides an **abstraction** that can be implemented by any vendor/provider. There are two providers implemented out of the box: * `LocalDistributedEventBus` is the default implementation that implements the distributed event bus to work as in-process. Yes! The **default implementation works just like the [local event bus](Local-Event-Bus.md)**, if you don't configure a real distributed provider. * `RabbitMqDistributedEventBus` implements the distributed event bus with the [RabbitMQ](https://www.rabbitmq.com/). See the [RabbitMQ integration document](Distributed-Event-Bus-RabbitMQ-Integration.md) to learn how to configure it. +Using a local event bus as default has a few important advantages. The most important one is that: It allows you to write your code compatible to distributed architecture. You can write a monolithic application now that can be split into microservices later. It is a good practice to communicate between bounded contexts (or between application modules) via distributed events instead of local events. + +For example, [pre-built application modules](Modules/Index.md) is designed to work as a service in a distributed system while they can also work as a module in a monolithic application without depending an external message broker. + ## Publishing Events +There are two ways of publishing distributed events explained in the following sections. + +### IDistributedEventBus + +`IDistributedEventBus` can be [injected](Dependency-Injection.md) and used to publish a distributed event. + +**Example: Publish a distributed event when the stock count of a product changes** + +````csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; + +namespace AbpDemo +{ + public class MyService : ITransientDependency + { + private readonly IDistributedEventBus _distributedEventBus; + + public MyService(IDistributedEventBus distributedEventBus) + { + _distributedEventBus = distributedEventBus; + } + + public virtual async Task ChangeStockCountAsync(Guid productId, int newCount) + { + await _distributedEventBus.PublishAsync( + new StockCountChangedEvent + { + ProductId = productId, + NewCount = newCount + } + ); + } + } +} +```` + +`PublishAsync` method gets a single parameter: the event object, which is responsible to hold the data related to the event. It is a simple plain class: + +````csharp +using System; + +namespace AbpDemo +{ + [EventName("MyApp.Product.StockChange")] + public class StockCountChangedEto + { + public Guid ProductId { get; set; } + + public int NewCount { get; set; } + } +} +```` + +Even if you don't need to transfer any data, you need to create a class (which is an empty class in this case). + +> `Eto` is a suffix for **E**vent **T**ransfer **O**bjects we use by convention. While it is not required, we find it useful to identify such event classes (just like [DTOs](Data-Transfer-Objects.md) on the application layer). + +#### Event Name + +`EventName` attribute is optional, but suggested. If you don't declare it, the event name will be the full name of the event class, `AbpDemo.StockCountChangedEto` in this case. + +#### About Serialization for the Event Objects + +Event transfer objects **must be serializable** since they will be serialized/deserialized to JSON or other format when it is transferred to out of the process. + +Avoid circular references, polymorphism, private setters and provide default (empty) constructors if you have any other constructor as a good practice (while some serializers may tolerate it), just like the DTOs. + +### Inside Entity / Aggregate Root Classes + +[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish distributed events inside entity / aggregate root classes. + +**Example: Publish a distributed event inside an aggregate root method** + +````csharp +using System; +using Volo.Abp.Domain.Entities; + +namespace AbpDemo +{ + public class Product : AggregateRoot + { + public string Name { get; set; } + + public int StockCount { get; private set; } + + private Product() { } + + public Product(Guid id, string name) + : base(id) + { + Name = name; + } + + public void ChangeStockCount(int newCount) + { + StockCount = newCount; + + //ADD an EVENT TO BE PUBLISHED + AddDistributedEvent( + new StockCountChangedEto + { + ProductId = Id, + NewCount = newCount + } + ); + } + } +} +```` + +`AggregateRoot` class defines the `AddDistributedEvent` to add a new distributed event, that is published when the aggregate root object is saved (created, updated or deleted) into the database. + +> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` method which guarantees publishing the event. + +#### IGeneratesDomainEvents Interface + +Actually, adding distributed events are not unique to the `AggregateRoot` class. You can implement `IGeneratesDomainEvents` for any entity class. But, `AggregateRoot` implements it by default and makes it easy for you. + +> It is not suggested to implement this interface for entities those are not aggregate roots, since it may not work for some database providers for such entities. It works for EF Core, but not works for MongoDB for example. + +#### How It Was Implemented? + +Calling the `AddDistributedEvent` doesn't immediately publish the event. The event is published when you save changes to the database; + +* For EF Core, it is published on `DbContext.SaveChanges`. +* For MongoDB, it is published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system). + +## Subscribing to Events + +A service can implement the `IDistributedEventHandler` to handle the event. + +**Example: Handle the `StockCountChangedEto` defined above** + +````csharp +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; + +namespace AbpDemo +{ + public class MyHandler + : IDistributedEventHandler, + ITransientDependency + { + public async Task HandleEventAsync(StockCountChangedEto eventData) + { + var productId = eventData.ProductId; + } + } +} +```` + +That's all. + +* `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEto` event occurs. +* If you are using a distributed message broker, like RabbitMQ, ABP automatically **subscribes to the event on the message broker**, gets the message, executes the handler. +* It sends **confirmation (ACK)** to the message broker if the event handler was successfully executed (did not throw any exception). + +You can inject any service and perform any required logic here. A single event handler class can **subscribe to multiple events** but implementing the `IDistributedEventHandler` interface for each event type. + +> The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. + +## Pre-Built Events + TODO \ No newline at end of file diff --git a/docs/en/Local-Event-Bus.md b/docs/en/Local-Event-Bus.md index 4452149f56..d0f494faea 100644 --- a/docs/en/Local-Event-Bus.md +++ b/docs/en/Local-Event-Bus.md @@ -10,7 +10,7 @@ There are two ways of publishing local events explained in the following section `ILocalEventBus` can be [injected](Dependency-Injection.md) and used to publish a local event. -**Example: Publish an event when the stock count of a product changes** +**Example: Publish a local event when the stock count of a product changes** ````csharp using System; @@ -66,9 +66,9 @@ Even if you don't need to transfer any data, you need to create a class (which i ### Inside Entity / Aggregate Root Classes -[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish events inside entity / aggregate root classes. +[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish local events inside entity / aggregate root classes. -**Example: Publish an event inside an aggregate root method** +**Example: Publish a local event inside an aggregate root method** ````csharp using System; @@ -107,7 +107,7 @@ namespace AbpDemo } ```` -`AggregateRoot` class defines the `AddLocalEvent` to add a new local event, that is published when the aggregate root object is saved (created or updated) into the database. +`AggregateRoot` class defines the `AddLocalEvent` to add a new local event, that is published when the aggregate root object is saved (created, updated or deleted) into the database. > If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` method which guarantees publishing the event. From 4c23b7fd1a84c8b37cb1c0c94e177f59b26fb8d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 18 Jun 2020 14:30:46 +0300 Subject: [PATCH 8/8] Update Distributed-Event-Bus.md --- docs/en/Distributed-Event-Bus.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/en/Distributed-Event-Bus.md b/docs/en/Distributed-Event-Bus.md index db6dd95cc3..c22e3c85e5 100644 --- a/docs/en/Distributed-Event-Bus.md +++ b/docs/en/Distributed-Event-Bus.md @@ -179,7 +179,3 @@ That's all. You can inject any service and perform any required logic here. A single event handler class can **subscribe to multiple events** but implementing the `IDistributedEventHandler` interface for each event type. > The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. - -## Pre-Built Events - -TODO \ No newline at end of file