Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Publishing domain events with MediatR
Pierre Bouillon
Pierre Bouillon

Posted on

     

Publishing domain events with MediatR

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:

~$dotnet new webapi-o MediatrNotification
Enter fullscreen modeExit fullscreen mode

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

Finally, add MediatR and initialize it:

~$dotnet add package MediatR~$dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
Enter fullscreen modeExit fullscreen mode
// Program.cs+ using MediatR;var builder = WebApplication.CreateBuilder(args);// Add services to the container.+ builder.Services.AddMediatR(typeof(Program));
Enter fullscreen modeExit fullscreen mode

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 filePlaceBouquetOrderRequest.cs:

// PlaceBouquetOrderRequest.cspublicclassPlaceBouquetOrderRequest:IRequest<Guid>{publicDateTimeDueDate{get;init;}publicintFlowersCount{get;init;}publicstring?Note{get;init;}}
Enter fullscreen modeExit fullscreen mode

and initialize the handler alongside it:

// PlaceBouquetOrderRequest.cspublicclassPlaceBouquetOrderRequestHandler:IRequestHandler<PlaceBouquetOrderRequest,Guid>{publicTask<Guid>Handle(PlaceBouquetOrderRequestrequest,CancellationTokencancellationToken){varorderId=newGuid();// Send the order to the merchantreturnTask.FromResult(orderId);}}
Enter fullscreen modeExit fullscreen mode

Finally, create the associated controller inControllers/BouquetController.cs:

// BouquetController.cs[Route("api/[controller]")]publicclassBouquetController:ControllerBase{privatereadonlyIMediator_mediator;publicBouquetController(IMediatormediator)=>_mediator=mediator;[HttpPost("order")]publicasyncTask<IActionResult>PlaceBouquetOrder([FromBody]PlaceBouquetOrderRequestrequest){varorderId=await_mediator.Send(request);returnOk(orderId);}}
Enter fullscreen modeExit fullscreen mode

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 toPlaceBouquetOrderRequest.cs and add the additional changes:

publicclassPlaceBouquetOrderRequestHandler:IRequestHandler<PlaceBouquetOrderRequest,Guid>{publicTask<Guid>Handle(PlaceBouquetOrderRequestrequest,CancellationTokencancellationToken){varorderId=newGuid();// Send the order to the merchantSendReminderToCalendarAt(request.DueDate);returnTask.FromResult(orderId);}privatevoidSendReminderToCalendarAt(DateTimedueDate){// Send a reminder to the merchant's calendar}}
Enter fullscreen modeExit fullscreen mode

The problem

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

  • OurPlaceBouquetOrderRequestHandler, once in charge of placing bouquets orders, is now also in charge of scheduling reminders: its scope is growing outside its original responsibility
  • TheSendReminder 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 calledNotifications.

Let's create one to solve our case !

In a newBouquetOrderPlacedEvent.cs, create the following event:

// BouquetOrderPlacedEvent.cspublicclassBouquetOrderPlacedEvent:INotification{publicGuidOrderId{get;init;}publicDateTimeDueDate{get;init;}}
Enter fullscreen modeExit fullscreen mode

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

// BouquetOrderPlacedEvent.cspublicclassBouquetOrderPlacedEventHandler:INotificationHandler<BouquetOrderPlacedEvent>{publicTaskHandle(BouquetOrderPlacedEventnotification,CancellationTokencancellationToken){SendReminderToCalendarAt(notification.DueDate);returnTask.CompletedTask;}privatevoidSendReminderToCalendarAt(DateTimedueDate){// Send a reminder to the merchant's calendar}}
Enter fullscreen modeExit fullscreen mode

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

// PlaceBouquetOrderRequestHandler.cspublicclassPlaceBouquetOrderRequestHandler:IRequestHandler<PlaceBouquetOrderRequest,Guid>{privatereadonlyIPublisher_publisher;publicPlaceBouquetOrderRequestHandler(IPublisherpublisher)=>_publisher=publisher;publicTask<Guid>Handle(PlaceBouquetOrderRequestrequest,CancellationTokencancellationToken){varorderId=newGuid();// Send the order to the merchant_publisher.Publish(newBouquetOrderPlacedEvent{OrderId=orderId,DueDate=request.DueDate});returnTask.FromResult(orderId);}}
Enter fullscreen modeExit fullscreen mode

Going further

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

// BouquetOrderPlacedEvent.cspublicabstractclassOrderPlacedEvent:INotification{publicGuidOrderId{get;init;}publicDateTimeDueDate{get;init;}}publicclassBouquetOrderPlacedEvent:OrderPlacedEvent{}
Enter fullscreen modeExit fullscreen mode

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

publicclassOrderPlacedEventHandler<TOrderPlacedEvent>:INotificationHandler<TOrderPlacedEvent>whereTOrderPlacedEvent:OrderPlacedEvent{publicTaskHandle(TOrderPlacedEventnotification,CancellationTokencancellationToken){SendReminderToCalendarAt(notification.DueDate);returnTask.CompletedTask;}privatevoidSendReminderToCalendarAt(DateTimedueDate){// Send a reminder to the merchant's calendar}}
Enter fullscreen modeExit fullscreen mode

Note that if we had just changed our handler's definition topublic class OrderPlacedEventHandler : INotificationHandler<OrderPlacedEvent>, MediatR would not have correctly route the event to our handler. You can read more about it onthis issue.

Take aways

Andvoilà, 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.

Top comments(2)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
rafalpienkowski profile image
Rafal Pienkowski
I'm focused on developing and expanding my knowledge and skills. Enjoying new challenges. I'm assuming that there are no stupid questions, there are only silly answers.
  • Location
    Bydgoszcz
  • Education
    Nicolaus Copernicus University
  • Work
    Principal Software Engineer at intive
  • Joined

It’s a lovely article describing the capabilities of MediatR. A thing that could interest you is the term “bounded context” introduced in Eric Evan's book. Also, a very underrated topic related to the bounded context is called “context map”.
In your example, I see two contexts: ordering and scheduling. Both are separated. That is why it makes sense to implement the logic in separate handlers. You introduce an event called “OrderPlacedEvent” in the Ordering context that has a side effect in the Scheduling context. The dependency between that two contexts could be reverted. A “ThingScheduledEvent” could make the job done. Now the The scheduling context is an “Upstream” context, and we could schedule things from other places without any change the handler is responsible for scheduling.

CollapseExpand
 
pbouillon profile image
Pierre Bouillon
Software Engineer | Full Stack Developer | Coffee Lover
  • Location
    France
  • Education
    Engineering degree
  • Pronouns
    He / Him
  • Work
    Full Stack Software Engineer
  • Joined

Thanks a lot for your feedback!

I didn't knew about the terminology, I will definitely check it out, it makes even more sense with a name on it!

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Software Engineer | Full Stack Developer | Coffee Lover
  • Location
    France
  • Education
    Engineering degree
  • Pronouns
    He / Him
  • Work
    Full Stack Software Engineer
  • Joined

More fromPierre Bouillon

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp