9

We are using MediatR to implement a "Pipeline" for our dotnet core WebAPI backend, trying to follow the CQRS principle.

I can't decide if I should try to implement a IPipelineBehavior chain, or if it is better to construct a new Request and call MediatR.Send from within my Handler method (for the request).

The scenario is essentially this:

  • User requests an action to be executed, i.e. Delete something
  • We have to check if that something is being used by someone else
  • We have to mark that something as deleted in the database
  • We have to actually delete the files from the file system.

Option 1 is what we have now: A DeleteRequest which is handled by one class, wherein the Handler checks if it is being used, marks it as deleted, and then sends a new TaskStartRequest with the parameters to Delete.

Option 2 is what I'm considering: A DeleteRequest which implements the marker interfaces IRequireCheck, IStartTask, with a pipeline which runs:

  1. IPipelineBehavior<IRequireCheck> first to check if the something is being used,
  2. IPipelineBehavior<DeleteRequest> to mark the something as deleted in database and
  3. IPipelineBehavior<IStartTask> to start the Task.

I haven't fully figured out what Option 2 would look like, but this is the general idea.

I guess I'm mainly wondering if it is code smell to call MediatR.Send(TRequest2) within a Handler for a TRequest1.

gakera
  • 3,589
  • 4
  • 30
  • 36
  • 1
    Were you able to implement your second option? I'm finding the docs on Pipelines and Behaviors lacking, in terms of providing enough context for me to translate them to my situation, which is similar to your #2. – Nate Feb 08 '20 at 19:09
  • We haven't implemented that functionality yet, it hasn't been a priority yet, but will be soon. I learned some interesting stuff about registering pipeline handlers tho, so it makes this more feasible. We do similar stuff with authentication, audit logging and some other cross cutting concerns. – gakera Feb 09 '20 at 20:05
  • In essence, we use reflection to register the handlers for the marker interface classes, so they are correctly registered to run only as Pre- or Post- processors for the corresponding Request or Response type. We use this for marker interfaces such as IAddToAuditLog. – gakera Feb 09 '20 at 20:18

2 Answers2

2

If those are the options you're set on going with - I say Option 2. Sending requests from inside existing Mediatr handlers can be seen as a code smell. You're hiding side effects and breaking the Single Responsibility Principle. You're also coupling your requests together and you should try to avoid situations where you can't send one type of request before another.

However, I think there might be an alternative. If a delete request can't happen without the validation and marking beforehand you may be able to leverage a preprocessor (example here) for your TaskStartRequest. That way you can have a single request that does everything you need. This even mirrors your pipeline example by simply leveraging the existing Mediatr patterns.

  • 1
    What about the case where the first or second check fails, how would we stop the rest of the processing ? In the Pipeline implementation, I think I have to call next() for the processing to continue, so I could skip calling next() if it should fail. – gakera Sep 25 '19 at 15:24
  • 1
    Hmm, that's a good point. In the past I've thrown an exception. So if the item we were deleting did not exist I threw a custom exception that's handled in middleware to result in a 404. If you would rather not result to using exceptions I think your pipeline idea is still better. Then you'd be able to have control over execution without exceptions. – Donald McPartland Sep 25 '19 at 20:10
0

Is there any need to break the tasks into multiple Handlers? Maybe I am missing the point in mediatr. Wouldn't this suffice?

public async Task<Result<IFailure,ISuccess>> Handle(DeleteRequest request)
{
  var thing = await this.repo.GetById(request.Id);

  if (thing.IsBeignUsed())
  {
    return Failure.BeignUsed();
  }
  var deleted = await this.repo.Delete(request.Id);
  return deleted ? new Success(request.Id) : Failure.DbError();
}
Chekkan
  • 76
  • 1
  • 5
  • 1
    This option assumes that `thing` is able to determine if its being used or not; which isn't always the case. – Nate Feb 08 '20 at 19:15
  • In our case, it's not just a matter of deleting from the repo, but also starting a task to delete the files. Tasks are started using a different processor, like I mentioned, and I dislike sending a different request from the body of a handler. – gakera Feb 09 '20 at 20:15
  • 2
    Would it make sense to send Notification instead or Request for the task to start? See https://github.com/jbogard/MediatR/issues/237 for similar question. – Oldrich Dlouhy Aug 19 '20 at 09:51