Fat Controller CQRS Diet: Trade-offs

Trade-OffsIn my Fat Controller CQRS Diet series, I’ve shown the mediator pattern and the MediatR library. After a recent discussion with Reid Evans, he made me realize I haven’t really described the trade-offs.  This post is going to focus on trade-offs and an alternative to using the mediator pattern.

If you’re new to this series, here are earlier posts to get you up to speed:

  1. Overview of Series
  2. Simple Query
  3. Simple Command
  4. Command Pipeline
  5. Notifications
  6. Vertical Slices

Coupling

With the mediator pattern, communication between objects is encapsulated with a mediator object. Objects no longer communicate directly with each other, but instead communicate through the mediator. This reduces the dependencies between communicating objects, thereby lowering the coupling.

For me the purpose of using the mediator pattern is to limit the coupling between integration boundaries.  Those boundaries in my applications are typically the Web/UI Framework and my core application.

This means I’ll have my controllers take a dependency on the mediator and not my core application.

Runtime Resolution

The downside to this that there is no compile time type checking.  When the mediator is invoked at runtime, it could fail to send the request if there is no handler defined.

In the code example above, if there is no IAsyncRequestHandler<Home, List<Album>> then an exception will be thrown on _mediator.SendAsync(new Home());

Coupling to Handler (or Interface)

We could get around this and get our type checking at compile time by having the request handler injected into our controller.

This is fairly reasonable since I often have one controller per command or query.  This then limits the dependencies I’d be putting into the constructor.

Messaging

One aspect that I haven’t touched on (yet) in this series is about background processing and queues.  I really enjoy using Hangfire to create background jobs.   I’ve blogged about it before and how to integrate MediatR and Hangfire together.

The general approach I take is to use messaging between MediatR and Hangfire to keep it consistent on how commands or queries are made.  That means messaging with MediatR done in-process or done on out of process (on other machines) via Hangfire but routing them through MediatR.

Comments

GitHub

I love hearing your comments, questions and recommendations!  Please post a comments or on Twitter.


  • Craig Wagner

    We use a similar concept in our application. The way we address the potential run-time failures is through a combination of convention-over-configuration naming standards, attributes, and unit tests. Our naming convention is that all commands end in Command and the thing that handles the command ends in CommandHandler. We also want to ensure that each command handler identifies the command it handles. It’s then easy enough in a unit test to grab all the commands and make sure a handler exists for each one, and to grab each command handler and ensure its corresponding command exists.

    • Thanks for sharing your approach!

      We run all our tests through the mediator so we can verify the proper registrations. I’ve been thinking about creating an analyzer with Roslyn. Good idea?

      • Craig Wagner

        IMO, if it works and prevents run-time failures it’s a good idea. We just use plain ol’ reflection in our tests because (a) it works, (b) everyone understands it, and (c) Roslyn wasn’t around when the tests were written. We do have a couple of other tests that look at the code a little more deeply to ensure a certain pattern is followed (although I keep suggesting we should look at PostSharp to solve that particular problem, but I digress). For that we use MonoCecil, again because Roslyn wasn’t available when the test was written. Those tests I’d like to rewrite using Roslyn because I think they’d be easier to understand, but it’s hard to justify the time when the tests are working.

  • Vegar Vikan

    I find the suggestion to inject and call the handler directly very strange.
    You are basically suggesting to go away from the mediator pattern?
    It want limit or reduce the number of dependencies either, since you are just switching out IMediator to IHomeHandler.

    • The trade-off with the loose coupling with the mediator pattern is no compile time type checking. Alternatives to this would be to create tests that verify your code base contains matching command + handlers. However if you want compile time type checking, then yes, an alternative is to depend on a concrete handler type or interface.

      • Vegar Vikan

        The missing compile time check is a trade-off, yes. That’s the thing about loose coupling: Everything becomes more … loose … 🙂

        Since the series from post 2 has been how to use mediator pattern to solve a problem, not using it doesn’t sound like a way of ‘getting around’ one of the challenges the pattern brings. IMHO it should be more clear from the text that this is what you suggest, so that readers don’t inject handlers and call them and think they still use mediator pattern and loose coupling…

        Anyway; Our solution – or more like a band aid really – is manual inspection. We have made a way of inspecting what handlers are registered in the solution, and also the connection between them (the pipeline – what notification handlers exist for the completion event for a request, and what notification handlers might result in new requests being sent).
        On top of that, we’ll add more tests on the IoC-container then what we have today.