Publishing domain events with MediatR
Tags: csharp, dotnet, tutorial, mediatr
Published: 2022-05-06 08:11:38

When implementing your business logic inside your handler, you may want to perform some actions as a side effect of your processing.

Having those in your handler might bloat your code or result in duplication if you want to perform some of them in several handler.

MediatR Notifications might come handy in such cases, let's see how !

## Setup

First, let's create setup our example. We will create a simple web API using .NET 6:

```bash
~$ dotnet new webapi -o MediatrNotification
```

In your favorite editor, remove any occurrences of the initial boilerplate (`WeatherForecast.cs` and `Controllers/WheatherForecastController.cs`)

Finally, add MediatR and initialize it:

```bash
~$ dotnet add package MediatR
~$ dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
```

```diff
// Program.cs
+ using MediatR;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
+ builder.Services.AddMediatR(typeof(Program));
```

## Use case

For our demo, we will have a single endpoint, allowing someone to order a bouquet of flowers.

Based on that example, we can create our request in a new file `PlaceBouquetOrderRequest.cs`:

```csharp
// PlaceBouquetOrderRequest.cs
public class PlaceBouquetOrderRequest : IRequest<Guid>
{
    public DateTime DueDate { get; init; }
    public int FlowersCount { get; init; }
    public string? Note { get; init; }
}
```

and initialize the handler alongside it:

```csharp
// PlaceBouquetOrderRequest.cs
public class PlaceBouquetOrderRequestHandler : IRequestHandler<PlaceBouquetOrderRequest, Guid>
{
    public Task<Guid> Handle(PlaceBouquetOrderRequest request, CancellationToken cancellationToken)
    {
        var orderId = new Guid();

        // Send the order to the merchant

        return Task.FromResult(orderId);
    }
}
```

Finally, create the associated controller in `Controllers/BouquetController.cs`:

```csharp
// BouquetController.cs
[Route("api/[controller]")]
public class BouquetController : ControllerBase
{
    private readonly IMediator _mediator;

    public BouquetController(IMediator mediator)
        => _mediator = mediator;

    [HttpPost("order")]
    public async Task<IActionResult> PlaceBouquetOrder([FromBody] PlaceBouquetOrderRequest request)
    {
        var orderId = await _mediator.Send(request);
        return Ok(orderId);
    }
}
```

## Adding the side effects

Our app is running great but now our client want us to also send an event to the merchant's calendar so that he can have an overview of its schedule.

> Please note that we won't perform any validation here since that's not the goal of this tutorial

Let's go back to `PlaceBouquetOrderRequest.cs` and add the additional changes:

```csharp
public class PlaceBouquetOrderRequestHandler : IRequestHandler<PlaceBouquetOrderRequest, Guid>
{
    public Task<Guid> Handle(PlaceBouquetOrderRequest request, CancellationToken cancellationToken)
    {
        var orderId = new Guid();

        // Send the order to the merchant
       SendReminderToCalendarAt(request.DueDate);

        return Task.FromResult(orderId);
    }

   private void SendReminderToCalendarAt(DateTime dueDate)
   {
       // Send a reminder to the merchant's calendar
   }
}
```

## The problem

Unfortunately, there are a couple issues that you might find from there:

- Our `PlaceBouquetOrderRequestHandler`, once in charge of placing bouquets orders, is now also in charge of scheduling reminders: its scope is growing outside its original responsibility
- The `SendReminder` logic could be reused somewhere else and would require either to duplicate the method or to extract it into a dedicated service. However, creating a service might result in an object altering the structure of the code, designed around handlers.

## A solution

If we take a moment to think about it, the action requested is more about "doing something when an order has been placed" rather that just sending a reminder.

Fortunately MediatR has such an object to represent those events and handling them, they are called **Notifications**.

Let's create one to solve our case !

In a new `BouquetOrderPlacedEvent.cs`, create the following event:

```csharp
// BouquetOrderPlacedEvent.cs
public class BouquetOrderPlacedEvent : INotification
{
    public Guid OrderId { get; init; }
    public DateTime DueDate { get; init; }
}
```

We can now create an event handler able to process those kind of notifications:

```csharp
// BouquetOrderPlacedEvent.cs
public class BouquetOrderPlacedEventHandler : INotificationHandler<BouquetOrderPlacedEvent>
{
    public Task Handle(BouquetOrderPlacedEvent notification, CancellationToken cancellationToken)
    {
        SendReminderToCalendarAt(notification.DueDate);

        return Task.CompletedTask;
    }

    private void SendReminderToCalendarAt(DateTime dueDate)
    {
        // Send a reminder to the merchant's calendar
    }
}
```

And replace our former logic in our handler by the emission of this event:

```csharp
// PlaceBouquetOrderRequestHandler.cs
public class PlaceBouquetOrderRequestHandler : IRequestHandler<PlaceBouquetOrderRequest, Guid>
{
    private readonly IPublisher _publisher;

    public PlaceBouquetOrderRequestHandler(IPublisher publisher)
        => _publisher = publisher;

    public Task<Guid> Handle(PlaceBouquetOrderRequest request, CancellationToken cancellationToken)
    {
        var orderId = new Guid();

        // Send the order to the merchant

         _publisher.Publish(new BouquetOrderPlacedEvent
        {
            OrderId = orderId,
            DueDate = request.DueDate
        });

        return Task.FromResult(orderId);
    }
}
```

## Going further

If we plan on handling new kinds of orders, we can generalize our event to an `OrderPlacedEvent` to abstract it from the kind of order it is:

```csharp
// BouquetOrderPlacedEvent.cs
public abstract class OrderPlacedEvent : INotification
{
    public Guid OrderId { get; init; }
    public DateTime DueDate { get; init; }
}

public class BouquetOrderPlacedEvent : OrderPlacedEvent { }
```

We can then make our handler generic so that it can handle any event derived from the base class `OrderPlacedEvent`:

```csharp
public class OrderPlacedEventHandler<TOrderPlacedEvent> : INotificationHandler<TOrderPlacedEvent>
    where TOrderPlacedEvent : OrderPlacedEvent
{
    public Task Handle(TOrderPlacedEvent notification, CancellationToken cancellationToken)
    {
        SendReminderToCalendarAt(notification.DueDate);

        return Task.CompletedTask;
    }

    private void SendReminderToCalendarAt(DateTime dueDate)
    {
        // Send a reminder to the merchant's calendar
    }
}
```

> Note that if we had just changed our handler's definition to `public class OrderPlacedEventHandler : INotificationHandler<OrderPlacedEvent>`, MediatR would not have correctly route the event to our handler. You can read more about it on [this issue](https://github.com/jbogard/MediatR/issues/308).

## Take aways

And *voilà*, we moved our logic into a dedicated handler, that might later also handle order of a different types. We also kept the logic of our handler as close as possible to its current use case and would also probably have reduced the handler's dependencies if we really implemented the communication with a third party calendar service.