Skip to content

Fat Controller CQRS Diet: Trade-offs

Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.

Learn more about Software Architecture & Design.
Join thousands of developers getting weekly updates to increase your understanding of software architecture and design concepts.


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.

8 thoughts on “Fat Controller CQRS Diet: Trade-offs”

  1. 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.

      1. 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.

  2. 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.

    1. 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.

      1. 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.

  3. I’m kind of curious of how and when other people use the Request / Query / Command (depending on if you’re using mediatR or a hand rolled ICommandHandler / IRequestHandler) pattern in their day-to-day.

    I’ve been using this pattern for years, and it makes everything so clean and neat. With post and pre-processing, wrappers for cross cutting concerns, it really is a powerful system with one caveat: class explosion.

    Now that we’ve moved away from IUserService / IUserRepository type interfaces (where one class violates the S in SOLID), a “Request” or a “Command” now requires upwards of 2-5 files (Request, RequestHandler, RequestValidator, and any other pre/post processors).

    If you use a request / command to do just about anything in the system, this can generate THOUSANDS of files depending on how large your project is. One way we have combated context switching & file explosion in the IDE is by placing the Request, the RequestHandler, and the Request validator in one file (I know I know, but digging through 3 projects / folders / files hurts my brain for one singular piece of functionality).

    I’d personally like some feedback on:

    1. Do you generate a Command / Request for every INSERT / UPDATE / DELETE operation? Or do you limit it to specifically “business-y” logic? We have been simply because I can wrap my permissions / security CCS to block unwanted users from performing certain operations with 1 PermissionHandlerDecorator and the usage of a PermissionAttribute.
    2. If not, how do you know when an operation should be wrapped in one?
    3. How do you combat file explosion and / or context/project switching if you use separate files/projects for each request / command?

    Any other tips would be great!

    Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *