Documentation Index Fetch the complete documentation index at: https://mintlify.com/IntentArchitect/Intent.Modules.NET/llms.txt
Use this file to discover all available pages before exploring further.
The Intent.DomainEvents module enables the domain event pattern, allowing entities to raise events that can be handled by event handlers throughout your application.
Overview
Domain events help implement:
Loose coupling between aggregates
Side effects from domain operations
Integration between bounded contexts
Audit trails and notifications
Eventual consistency patterns
Installation
Key Components
Domain Event Base
All domain events inherit from a base class:
public abstract class DomainEvent
{
public DateTimeOffset OccurredOn { get ; protected set ; } = DateTimeOffset . UtcNow ;
}
Has Domain Event Interface
Entities that raise events implement IHasDomainEvent:
public interface IHasDomainEvent
{
List < DomainEvent > DomainEvents { get ; }
}
public class Customer : IHasDomainEvent
{
private readonly List < DomainEvent > _domainEvents = new ();
public List < DomainEvent > DomainEvents => _domainEvents ;
public void AddDomainEvent ( DomainEvent domainEvent )
{
_domainEvents . Add ( domainEvent );
}
public void RemoveDomainEvent ( DomainEvent domainEvent )
{
_domainEvents . Remove ( domainEvent );
}
public void ClearDomainEvents ()
{
_domainEvents . Clear ();
}
}
Domain Event Service
public interface IDomainEventService
{
Task Publish ( DomainEvent domainEvent );
}
Creating Domain Events
Define domain events in the domain designer:
public class CustomerRegisteredEvent : DomainEvent
{
public CustomerRegisteredEvent ( Guid customerId , string email )
{
CustomerId = customerId ;
Email = email ;
}
public Guid CustomerId { get ; }
public string Email { get ; }
}
public class OrderPlacedEvent : DomainEvent
{
public OrderPlacedEvent ( Guid orderId , Guid customerId , decimal total )
{
OrderId = orderId ;
CustomerId = customerId ;
Total = total ;
}
public Guid OrderId { get ; }
public Guid CustomerId { get ; }
public decimal Total { get ; }
}
Raising Domain Events
Raise events from entity methods:
public class Customer : IHasDomainEvent
{
public Customer ( string name , string email )
{
Id = Guid . NewGuid ();
Name = name ;
Email = email ;
// Raise domain event
AddDomainEvent ( new CustomerRegisteredEvent ( Id , Email ));
}
public void PlaceOrder ( Order order )
{
Orders . Add ( order );
// Raise domain event
AddDomainEvent ( new OrderPlacedEvent ( order . Id , Id , order . Total ));
}
}
Event Handlers
When using with MediatR (Intent.MediatR.DomainEvents module):
public class CustomerRegisteredEventHandler : INotificationHandler < CustomerRegisteredEvent >
{
private readonly IEmailService _emailService ;
private readonly ILogger < CustomerRegisteredEventHandler > _logger ;
public CustomerRegisteredEventHandler (
IEmailService emailService ,
ILogger < CustomerRegisteredEventHandler > logger )
{
_emailService = emailService ;
_logger = logger ;
}
public async Task Handle (
CustomerRegisteredEvent notification ,
CancellationToken cancellationToken )
{
_logger . LogInformation (
"Sending welcome email to customer {CustomerId}" ,
notification . CustomerId );
await _emailService . SendWelcomeEmailAsync (
notification . Email ,
cancellationToken );
}
}
Multiple Handlers
Multiple handlers can respond to the same event:
public class OrderPlacedEventHandler : INotificationHandler < OrderPlacedEvent >
{
private readonly IInventoryService _inventoryService ;
public async Task Handle (
OrderPlacedEvent notification ,
CancellationToken cancellationToken )
{
// Reserve inventory
await _inventoryService . ReserveInventoryAsync (
notification . OrderId ,
cancellationToken );
}
}
public class OrderPlacedNotificationHandler : INotificationHandler < OrderPlacedEvent >
{
private readonly INotificationService _notificationService ;
public async Task Handle (
OrderPlacedEvent notification ,
CancellationToken cancellationToken )
{
// Send notification
await _notificationService . NotifyCustomerAsync (
notification . CustomerId ,
$"Order { notification . OrderId } placed successfully" ,
cancellationToken );
}
}
public class OrderPlacedAuditHandler : INotificationHandler < OrderPlacedEvent >
{
private readonly IAuditService _auditService ;
public async Task Handle (
OrderPlacedEvent notification ,
CancellationToken cancellationToken )
{
// Log audit trail
await _auditService . LogAsync (
"OrderPlaced" ,
notification . OrderId ,
notification ,
cancellationToken );
}
}
Event Publishing
Automatic Publishing with EF Core
When using Intent.EntityFrameworkCore.Interop.DomainEvents, events are automatically published on SaveChanges:
public class ApplicationDbContext : DbContext
{
private readonly IDomainEventService _domainEventService ;
public override async Task < int > SaveChangesAsync (
CancellationToken cancellationToken = default )
{
// Collect domain events before saving
var domainEvents = ChangeTracker
. Entries < IHasDomainEvent >()
. SelectMany ( x => x . Entity . DomainEvents )
. ToList ();
// Save changes
var result = await base . SaveChangesAsync ( cancellationToken );
// Publish domain events after successful save
foreach ( var domainEvent in domainEvents )
{
await _domainEventService . Publish ( domainEvent );
}
// Clear events
foreach ( var entry in ChangeTracker . Entries < IHasDomainEvent >())
{
entry . Entity . ClearDomainEvents ();
}
return result ;
}
}
Manual Publishing
public class CustomerService
{
private readonly ICustomerRepository _repository ;
private readonly IDomainEventService _domainEventService ;
public async Task RegisterCustomerAsync ( string name , string email )
{
var customer = new Customer ( name , email );
_repository . Add ( customer );
await _repository . UnitOfWork . SaveChangesAsync ();
// Manually publish events
foreach ( var domainEvent in customer . DomainEvents )
{
await _domainEventService . Publish ( domainEvent );
}
customer . ClearDomainEvents ();
}
}
Event Ordering
Control event handling order when needed:
public class HighPriorityHandler : INotificationHandler < OrderPlacedEvent >
{
public int Order => 1 ; // Executes first
public async Task Handle (
OrderPlacedEvent notification ,
CancellationToken cancellationToken )
{
// Critical operations
}
}
public class LowPriorityHandler : INotificationHandler < OrderPlacedEvent >
{
public int Order => 100 ; // Executes later
public async Task Handle (
OrderPlacedEvent notification ,
CancellationToken cancellationToken )
{
// Non-critical operations
}
}
Practical Examples
Order Processing Workflow
public class Order : IHasDomainEvent
{
public void Submit ()
{
if ( Status != OrderStatus . Draft )
throw new InvalidOperationException ( "Only draft orders can be submitted" );
Status = OrderStatus . Submitted ;
SubmittedDate = DateTime . UtcNow ;
AddDomainEvent ( new OrderSubmittedEvent ( Id , CustomerId , Total ));
}
public void Approve ()
{
if ( Status != OrderStatus . Submitted )
throw new InvalidOperationException ( "Only submitted orders can be approved" );
Status = OrderStatus . Approved ;
ApprovedDate = DateTime . UtcNow ;
AddDomainEvent ( new OrderApprovedEvent ( Id , CustomerId ));
}
public void Ship ( string trackingNumber )
{
if ( Status != OrderStatus . Approved )
throw new InvalidOperationException ( "Only approved orders can be shipped" );
Status = OrderStatus . Shipped ;
ShippedDate = DateTime . UtcNow ;
TrackingNumber = trackingNumber ;
AddDomainEvent ( new OrderShippedEvent ( Id , CustomerId , trackingNumber ));
}
}
// Handlers
public class OrderSubmittedEventHandler : INotificationHandler < OrderSubmittedEvent >
{
public async Task Handle ( OrderSubmittedEvent notification , CancellationToken cancellationToken )
{
// Send to approval queue
// Check inventory
// Notify customer
}
}
public class OrderApprovedEventHandler : INotificationHandler < OrderApprovedEvent >
{
public async Task Handle ( OrderApprovedEvent notification , CancellationToken cancellationToken )
{
// Reserve inventory
// Send to warehouse
// Update customer credit
}
}
public class OrderShippedEventHandler : INotificationHandler < OrderShippedEvent >
{
public async Task Handle ( OrderShippedEvent notification , CancellationToken cancellationToken )
{
// Send tracking email
// Update inventory
// Schedule delivery notification
}
}
Integration Events
Convert domain events to integration events:
public class OrderPlacedIntegrationHandler : INotificationHandler < OrderPlacedEvent >
{
private readonly IEventBus _eventBus ;
public async Task Handle (
OrderPlacedEvent notification ,
CancellationToken cancellationToken )
{
// Publish to message bus for other bounded contexts
var integrationEvent = new OrderPlacedIntegrationEvent
{
OrderId = notification . OrderId ,
CustomerId = notification . CustomerId ,
Total = notification . Total ,
OccurredOn = notification . OccurredOn
};
await _eventBus . PublishAsync ( integrationEvent , cancellationToken );
}
}
Error Handling
public class ResilientEventHandler : INotificationHandler < CustomerRegisteredEvent >
{
private readonly IEmailService _emailService ;
private readonly ILogger _logger ;
public async Task Handle (
CustomerRegisteredEvent notification ,
CancellationToken cancellationToken )
{
try
{
await _emailService . SendWelcomeEmailAsync (
notification . Email ,
cancellationToken );
}
catch ( Exception ex )
{
_logger . LogError ( ex ,
"Failed to send welcome email to {Email}" ,
notification . Email );
// Don't throw - allow other handlers to execute
// Could also queue for retry
}
}
}
Testing
public class CustomerTests
{
[ Fact ]
public void Constructor_ShouldRaiseCustomerRegisteredEvent ()
{
// Arrange
var name = "John Doe" ;
var email = "john@example.com" ;
// Act
var customer = new Customer ( name , email );
// Assert
var domainEvent = customer . DomainEvents
. OfType < CustomerRegisteredEvent >()
. Single ();
Assert . Equal ( customer . Id , domainEvent . CustomerId );
Assert . Equal ( email , domainEvent . Email );
}
[ Fact ]
public async Task Handler_ShouldSendWelcomeEmail ()
{
// Arrange
var mockEmailService = new Mock < IEmailService >();
var handler = new CustomerRegisteredEventHandler (
mockEmailService . Object ,
Mock . Of < ILogger < CustomerRegisteredEventHandler >>());
var domainEvent = new CustomerRegisteredEvent (
Guid . NewGuid (),
"john@example.com" );
// Act
await handler . Handle ( domainEvent , CancellationToken . None );
// Assert
mockEmailService . Verify (
x => x . SendWelcomeEmailAsync (
"john@example.com" ,
It . IsAny < CancellationToken >()),
Times . Once );
}
}
Best Practices
Immutable Events Make domain events immutable with readonly properties to prevent tampering.
Clear Naming Use past-tense names (CustomerRegistered, OrderPlaced) to indicate completed actions.
Event Data Include sufficient data in events to avoid additional queries in handlers.
Idempotent Handlers Design handlers to be idempotent in case events are processed multiple times.
Additional Resources