Mediatr: Combining Notification and Request/Response handlers

Created on 19 Feb 2018  路  5Comments  路  Source: jbogard/MediatR

I have implemented a CreateEdit query/command (based on Contoso Univerity example) but I would also like to add an additional handler to send a confirmation email after a successfully creating or updating an entity.

I have looked at the AsyncNotificationHandler to get all the handlers to respond but the notification handler cant return values?

I'm assuming my post edit controller code would look like this:
var id = await _mediator.Publish(command);
But it looks like notification command can't return values. I am obviously missing something but can't figure it out?

I this even possible?

Most helpful comment

As far as I understand MediatR, exceptions that happen inside notification handlers won't get caught. So notifications aren't truly fire-and-forget; we still wait for them to complete, and there can be exceptions that bubble up the stack. That means that it is, in fact, possible to do exception handling around the Publish call, e.g. handle some sort of EmailSendingFailedException.

But I completely agree that it shouldn't be done that way.

Ideally, write your notification handlers in a way so that they do as little work as possible, something that is fast and will never crash. That way you don't have to do any exception handling around notification handlers or wait too long for them to complete.

To illustrate this principle on your scenario:

  • have CreateEntity/EditEntity commands and handlers;
  • have a separate EntityUpdated notification;
  • inside the command handlers for CreateEntity/EditEntity commands, after we save the changes (ideally after the transaction is committed), I'd publish the EntityUpdated notification (and not handle any exceptions);
  • have a notification handler for EntityUpdated notification, which would ideally do as little work as possible, i.e. store it somewhere that we need to send the email -- this could be some sort of message queue, or just a database table, or you could schedule a background task (e. g. using Hangfire);
  • have a background job that handles all email sending, which can, ideally, do retries when the email server is unavailable.

All 5 comments

Make a clear distinction between commands and events, something like this:

_mediatr.Send(new DoSomething());

DoSomethingHandler:

// do something
// ...
_mediatr.Publish(new SomethingHasHappened());

SomethingHasHappenedHandler:

// send confirmation e-mail.

@remcoros Good idea. One question though how would I capture a failure when trying to send an email as the Publish event cannot return anything?

Publishing notifications are fire-and-forget.

If you want to capture a failure on the calling side, it's not "fire and forget" and you shouldn't use notifications.

Imo, sending an e-mail should be fire-and-forget from the call site. You would handle a failure by adding failed e-mails to a queue and have some other (background) service retry sending the e-mail.

If that's too much for your scenario, then you're back to chaining commands or handle everything inside a single handler.

Jimmy has some nice blogs about these subjects here: https://jimmybogard.com/refactoring-towards-resilience-a-primer/

As far as I understand MediatR, exceptions that happen inside notification handlers won't get caught. So notifications aren't truly fire-and-forget; we still wait for them to complete, and there can be exceptions that bubble up the stack. That means that it is, in fact, possible to do exception handling around the Publish call, e.g. handle some sort of EmailSendingFailedException.

But I completely agree that it shouldn't be done that way.

Ideally, write your notification handlers in a way so that they do as little work as possible, something that is fast and will never crash. That way you don't have to do any exception handling around notification handlers or wait too long for them to complete.

To illustrate this principle on your scenario:

  • have CreateEntity/EditEntity commands and handlers;
  • have a separate EntityUpdated notification;
  • inside the command handlers for CreateEntity/EditEntity commands, after we save the changes (ideally after the transaction is committed), I'd publish the EntityUpdated notification (and not handle any exceptions);
  • have a notification handler for EntityUpdated notification, which would ideally do as little work as possible, i.e. store it somewhere that we need to send the email -- this could be some sort of message queue, or just a database table, or you could schedule a background task (e. g. using Hangfire);
  • have a background job that handles all email sending, which can, ideally, do retries when the email server is unavailable.

Everyone, thank you for some great tips and advice.

Was this page helpful?
0 / 5 - 0 ratings