# Mastering Domain Events Made Simple with MediatR in C# and .NET 8

Readers of this article will gain insights into mastering **domain events** with **Meditr** in **C#** and **.NET 8**. They'll learn how to effectively manage **domain events**, from **creation** to **handling**, using this powerful library. Through step-by-step guidance, they'll discover practical techniques for optimizing **event-driven architectures**, enhancing **scalability** and **maintainability** in their software projects. To structure our discussion, we will use the **What? Why? How?** framework.

### **What?**

Domain events are a design pattern in **Domain-Driven Design (DDD)** used to capture and broadcast significant state changes in your domain model. In the context of **C#** with **Entity Framework Core 8** and **MediatR**, domain events enable you to decouple the core logic of your application from side effects, such as notifications or external system updates.

**MediatR** is a popular library in the .NET ecosystem that facilitates **CQRS (Command Query Responsibility Segregation)** and **mediator patterns**. It helps to manage interactions between objects, reducing dependencies and promoting a clean, maintainable codebase. MediatR uses handlers to process requests, such as commands, queries, and notifications, allowing for better separation of concerns.

### **Why?**

**Separation of Concerns:** By using domain events, you ensure that your core **domain logic** remains clean and focused on **business rules**, while other concerns (like **notifications** or **logging**) are handled separately.

**Decoupling:** Domain events help to decouple different parts of your application, promoting a more **modular** and **maintainable architecture**.

**Testability:** With domain events, you can more easily **test** your core domain logic without worrying about **side effects**, improving the overall **testability** of your application.

### **How?**

To implement domain events using **C#** with **Entity Framework Core 8** and **MediatR**, follow these steps:

**Capturing Domain Events**

#### 1. **Define a Basic Interface for Domain Events:**
    
    Create an interface `IDomainEvent` that inherits from MediatR's `INotification`. This interface will represent the domain events.
    
    ```csharp
    namespace RecipeManagement.Domain
    {
        using MediatR;
    
        public interface IDomainEvent : INotification { }
    }
    ```
    
#### 2. **Create a Base Entity Class:**
    
    Develop a base entity class that all your entities can inherit from. This class will include:
    
    * A list of `IDomainEvent` called `DomainEvents` to capture messages we want to publish.
        
    * A primary key property called `Id` for entity identification.
        
    * A method `QueueDomainEvent` to add events to the `DomainEvents` list.
        
    
    ```csharp
    namespace RecipeManagement.Domain
    {
        using System;
        using System.Collections.Generic;
        using System.ComponentModel.DataAnnotations;
        using System.ComponentModel.DataAnnotations.Schema;
    
        public abstract class BaseEntity
        {
            [Key]
            public Guid Id { get; private set; } = Guid.NewGuid();
    
            [NotMapped]
            public List<IDomainEvent> DomainEvents { get; } = new List<IDomainEvent>();
    
            public void QueueDomainEvent(IDomainEvent @event)
            {
                DomainEvents.Add(@event);
            }
        }
    }
    ```
    
#### 3. **Define and Publish the Domain Event:**
    
    Create a domain event class that implements `IDomainEvent`. Raise this event in your domain model.
    
    ```csharp
    public class OrderPlacedEvent : IDomainEvent
    {
        public int OrderId { get; }
        public DateTime OrderDate { get; }
    
        public OrderPlacedEvent(int orderId, DateTime orderDate)
        {
            OrderId = orderId;
            OrderDate = orderDate;
        }
    }
    
    public class Order : BaseEntity
    {
        public DateTime OrderDate { get; private set; }
    
        public void PlaceOrder(DateTime orderDate)
        {
            OrderDate = orderDate;
            QueueDomainEvent(new OrderPlacedEvent(Id, orderDate));
        }
    }
    ```
    
#### 4. **Save and Dispatch Domain Events:**
    
    Customize your `DbContext` to intercept the `SaveChanges` and `SaveChangesAsync` methods and dispatch the domain events after the changes are committed by encapsulating the dispatch logic in a method called `DispatchDomainEvents`.
    
    ```csharp
    public class AppDbContext : DbContext
    {
        private readonly IMediator _mediator;
    
        public AppDbContext(DbContextOptions<AppDbContext> options, IMediator mediator)
            : base(options)
        {
            _mediator = mediator;
        }
    
        public override int SaveChanges()
        {
            OnBeforeSaving().GetAwaiter().GetResult();
            return base.SaveChanges();
        }
    
        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
        {
            await OnBeforeSaving();
            return await base.SaveChangesAsync(cancellationToken);
        }
    
        private async Task OnBeforeSaving()
        {
            await DispatchDomainEvents();
        }
    
        private async Task DispatchDomainEvents()
        {
            var domainEventEntities = ChangeTracker.Entries<BaseEntity>()
                .Select(po => po.Entity)
                .Where(po => po.DomainEvents.Any())
                .ToArray();
    
            foreach (var entity in domainEventEntities)
            {
                var events = entity.DomainEvents.ToArray();
                entity.DomainEvents.Clear();
                foreach (var entityDomainEvent in events)
                {
                    await _mediator.Publish(entityDomainEvent);
                }
            }
        }
    }
    ```
    
#### 5. **Handle the Domain Event:**
    
    Implement a handler for your domain event. The handler should implement the `INotificationHandler` interface from MediatR.
    
    ```csharp
    public class OrderPlacedEventHandler : INotificationHandler<OrderPlacedEvent>
    {
        public Task Handle(OrderPlacedEvent notification, CancellationToken cancellationToken)
        {
            // Handle the event (e.g., send an email notification)
            Console.WriteLine($"Order placed with ID: {notification.OrderId} on {notification.OrderDate}");
            return Task.CompletedTask;
        }
    }
    ```
    
#### 6. **Register Dependencies:**
    
    Finally, register MediatR and your handlers in the dependency injection container.
    
    ```csharp
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<AppDbContext>(options => 
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    
            services.AddMediatR(typeof(Startup).Assembly);
            // Register other services and handlers
        }
    }
    ```
    

By following these steps, you can effectively implement **domain events** in your **C#** application using **Entity Framework Core 8** and **MediatR**. This approach helps you maintain a clean **separation of concerns**, decouple your application's components, and improve **testability**.
