Dormito.DomainEvents
5.0.0
dotnet add package Dormito.DomainEvents --version 5.0.0
NuGet\Install-Package Dormito.DomainEvents -Version 5.0.0
<PackageReference Include="Dormito.DomainEvents" Version="5.0.0" />
<PackageVersion Include="Dormito.DomainEvents" Version="5.0.0" />
<PackageReference Include="Dormito.DomainEvents" />
paket add Dormito.DomainEvents --version 5.0.0
#r "nuget: Dormito.DomainEvents, 5.0.0"
#:package Dormito.DomainEvents@5.0.0
#addin nuget:?package=Dormito.DomainEvents&version=5.0.0
#tool nuget:?package=Dormito.DomainEvents&version=5.0.0
Library to help implement transactional events in domain bounded context.
Use domain events to explicitly implement side effects of changes within your domain. In other words, and using DDD terminology, use domain events to explicitly implement side effects across multiple aggregates.
What is a Domain Event?
An event is something that has happened in the past. A domain event is, something that happened in the domain that you want other parts of the same domain (in-process) to be aware of. The notified parts usually react somehow to the events.
The domain events and their side effects (the actions triggered afterwards that are managed by event handlers) should occur almost immediately, usually in-process, and within the same domain.
It's important to ensure that, just like a database transaction, either all the operations related to a domain event finish successfully or none of them do.
Figure below shows how consistency between aggregates is achieved by domain events. When the user initiates an order, the Order Aggregate sends an OrderStarted domain event. The OrderStarted domain event is handled by the Buyer Aggregate to create a Buyer object in the ordering microservice (bounded context). Please read Domain Events for more details.

Two Approaches to Use DomainEvents
Approach 1: Using Publisher and Handler Directly
Define, publish, and subscribe to events using IPublisher and IHandler.
1. Define an Event
public class CustomerCreated : IDomainEvent
{
public string Name { get; set; }
}
2. Create a Handler
public class CustomerCreatedHandler : IHandler<CustomerCreated>
{
public Task HandleAsync(CustomerCreated @event)
{
Console.WriteLine($"Customer created: {@event.Name}");
return Task.CompletedTask;
}
}
3. Register Services
services.AddDomainEvents(typeof(CustomerCreatedHandler).Assembly);
4. Publish Events
var publisher = serviceProvider.GetRequiredService<IPublisher>();
await publisher.RaiseAsync(new CustomerCreated { Name = "John Doe" });
Approach 2: Using Interception (Aggregate + Factory)
Raise events automatically from domain aggregates using Castle DynamicProxy interception.
1. Define an Event
public class OrderPlaced : IDomainEvent
{
public string OrderId { get; set; }
public decimal Amount { get; set; }
}
2. Create an Aggregate (Publisher)
public class OrderAggregate : Aggregate
{
public void PlaceOrder(decimal amount)
{
// Business logic here...
var @event = new OrderPlaced
{
OrderId = Guid.NewGuid().ToString(),
Amount = amount
};
Raise(@event);
}
}
public class WarehouseAggregate : Aggregate, ISubscribes<OrderPlaced>
{
public Task HandleAsync(OrderPlaced @event)
{
Console.WriteLine($"Order created: {@event.OrderId}");
return Task.CompletedTask;
}
}
3. Register Services
services.AddDomainEvents(typeof(OrderPlacedHandler).Assembly);
4. Create Aggregate and Raise Event
var factory = serviceProvider.GetRequiredService<IAggregateFactory>();
var order = await factory.CreateAsync<OrderAggregate>();
order.PlaceOrder(100.00m); // Event is automatically dispatched to handlers
Architecture Flow
Event Processing Flow
┌────────────────────────────────────────────────────────────────────────────────┐
│ PUBLISHING PHASE │
│ (Aggregate.Raise() → Queue.Enqueue) │
└────────────────────────────────────────────────────────────────────────────────┘
Aggregate.Raise()
│
▼
┌───────────┐ ┌───────────┐ ┌────────────┐ ┌────────────┐
│ Aggregate │────▶│Interceptor│────▶│ Middleware│────▶│ Dispatcher │
│ │ │ (Proxy) │ │(OnDispatch)│ │ │
└───────────┘ └───────────┘ └────────────┘ └─────┬──────┘
│
▼
┌───────────────┐
│ Queue │
│ (In-Memory) │
└───────────────┘
┌────────────────────────────────────────────────────────────────────────────────┐
│ SUBSCRIPTION PHASE │
│ (Queue → Listener → Handler) │
└────────────────────────────────────────────────────────────────────────────────┘
Queue notifies Listener
│
▼
┌───────────┐ ┌────────────┐ ┌───────────┐ ┌───────────┐
│ Listener │────▶│ Middleware │────▶│ Resolver │────▶│ Handler │
│ │ │(OnHandling)│ │ │ │ │
└───────────┘ └────────────┘ └─────┬─────┘ └───────────┘
│
▼
┌──────────────────┐
│ IHandler<T> │
│ ISubscribes<T> │
│ (includes │
│ aggregates) │
└──────────────────┘
Flow Summary
- PUBLISHING PHASE - Aggregate.Raise() → Interceptor → Middleware.OnDispatching() → Dispatcher → Queue.Enqueue() → Middleware.OnDispatched()
- SUBSCRIPTION PHASE - Queue notifies Listener → Middleware.OnHandling() → Resolver (finds handlers) → Handler.HandleAsync() (includes ISubscribes<T>) → Middleware.OnHandled()
Note: ISubscribes - Aggregates can implement ISubscribes<TEvent> to handle events they raise. The proxy ensures both business logic AND handler execute.
Components:
- Aggregate - Domain aggregate that raises events via
Raise()orRaiseAsync(). Can also implementISubscribes<TEvent>. - Interceptor - Castle DynamicProxy that intercepts
Raise()/RaiseAsync()and dispatches events. - Middleware - Custom plugins:
OnDispatching,OnDispatched,OnHandling,OnHandled. - Dispatcher - Enqueues events to the queue.
- Queue - In-memory queue (fire-and-forget).
- Listener - Processes events from queue asynchronously.
- Resolver - Resolves handlers for events.
- Handler - Handles events:
IHandler<T>orISubscribes<T>.
Event Middleware
Custom plugins that run at various points in the event pipeline:
public class MyMiddleware : IEventMiddleware
{
public Task<bool> OnDispatchingAsync(EventContext context)
{
// Runs before event is dispatched
return Task.FromResult(true);
}
public Task OnDispatchedAsync(EventContext context)
{
// Runs after event is dispatched
return Task.CompletedTask;
}
public Task<bool> OnHandlingAsync(EventContext context)
{
// Runs before each handler processes the event
return Task.FromResult(true);
}
public Task OnHandledAsync(EventContext context)
{
// Runs after each handler processes the event
return Task.CompletedTask;
}
}
Registration:
services.AddDomainEvents(assembly); // auto-registers handlers and middlewares which have parameter-less constructor. For types with parameterized constructor, you need to explicitly register as below.
services.AddSingleton<IEventMiddleware, MyMiddleware>();
AggregateFactory Methods
The IAggregateFactory provides multiple methods to create proxied aggregates:
| Method | Description |
|---|---|
CreateAsync<T>() |
Creates proxy using default constructor |
CreateAsync<T>(params object[]) |
Creates proxy with specified constructor arguments |
CreateAsync(Type, params object[]) |
Non-generic version with constructor arguments |
CreateFromInstanceAsync<T>(T aggregate) |
Wraps existing aggregate instance in proxy |
CreateFromServiceProviderAsync<T>() |
Resolves from DI and wraps in proxy (auto-resolves constructor dependencies) |
CreateFromServiceProviderAsync(Type) |
Non-generic version resolving from DI |
Example - Using CreateFromServiceProviderAsync:
// Register aggregate with DI (constructor dependencies auto-resolved)
services.AddTransient<OrderAggregate>();
var factory = serviceProvider.GetRequiredService<IAggregateFactory>();
// Creates proxy, resolves OrderAggregate from DI, wraps in proxy
var order = await factory.CreateFromServiceProviderAsync<OrderAggregate>();
order.PlaceOrder(100.00m); // Events dispatched automatically
Note: When using CreateFromServiceProviderAsync, all constructor dependencies must be registered with the IoC container. The factory uses reflection to find the constructor with most parameters and resolves them from the service provider.
Interface Summary
| Interface | Purpose |
|---|---|
IDomainEvent |
Marker interface for domain events |
IHandler<TEvent> |
Async handler interface |
ISubscribes<TEvent> |
Aggregate handler interface (implemented by aggregates to handle their own events) |
IPublisher |
Interface for raising events |
IAggregateFactory |
Factory for creating proxied aggregates |
IEventMiddleware |
Plugin for event pipeline |
IEventQueue |
In-flight event queue |
Package Information
- Package ID:
Dormito.DomainEvents - Target Frameworks: netstandard2.0, netstandard2.1, net8.0, net9.0, net10.0
- License: MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Castle.Core (>= 5.2.1)
- Microsoft.Extensions.DependencyInjection (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- OpenTelemetry.Api (>= 1.15.0)
-
.NETStandard 2.1
- Castle.Core (>= 5.2.1)
- Microsoft.Extensions.DependencyInjection (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- OpenTelemetry.Api (>= 1.15.0)
-
net10.0
- Castle.Core (>= 5.2.1)
- Microsoft.Extensions.DependencyInjection (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- OpenTelemetry.Api (>= 1.15.0)
-
net8.0
- Castle.Core (>= 5.2.1)
- Microsoft.Extensions.DependencyInjection (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- OpenTelemetry.Api (>= 1.15.0)
-
net9.0
- Castle.Core (>= 5.2.1)
- Microsoft.Extensions.DependencyInjection (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- OpenTelemetry.Api (>= 1.15.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Release v5.0.0 - Major update with new features, improvements, and bug fixes.
This release includes enhanced aggregate support, improved event handling performance, and better integration with Castle DynamicProxy.
For more details, see the release notes on GitHub: https://github.com/CodeShayk/DomainEvents/releases/tag/v5.0.0