Initially, this article was called “How MVC became an anti-pattern. And Mediator became the pattern.”. But as I was writing I realized that we’ve got the wrong guy here. Probably you already know that I’ll attempt to blame Controllers in everything. Although MVC as we know it played an important role in our perception of the Controllers. I don’t think MVC is a true source of evil. I don’t even think that Controllers as part of the MVC pattern are evil. However, there’s definitely something wrong with them. Let’s figure out what.๐ค
The MVC
Model-View-Controller (or simply MVC) has been around for a while. It started as a back-end pattern, at that time SPA was not even a term and top of the line web sites had a couple of jQuery manipulations on the client. However, with evolving front-end, the MVC pattern quickly became a standing ground for a variety of front-end tools and frameworks, such as Angular.
I think the pattern itself makes a lot of sense if we zoom out from particular framework implementation to the conceptual level. Unfortunately, this is not how we as developers often adopt and eventually perceive patterns. A lot of the time we see them through the prism of a particular framework or a tool. The way many frameworks implement and impose the pattern (I’m looking at you Ruby on Rails and ASP.NET) play a crucial role in its perception by the community. I find that the most misleading part is a direct one-to-one mapping of the main pattern modeling elements (model, view, and controller) to the code.
One ring to rule them all
I did some web development involving different incarnations of the ASP.NET. It has been a while since I noticed that MVC and Controllers, in particular, started feeling like the only tool for any problem which somehow involved HTTP. Controllers are the default (and often the only known) way of building anything that requires HTTP communication, whether or not you truly need them. The framework imposes controllers on you. And instead of looking for the right tool to accomplish the task, developers start adjusting tasks for a tool.
I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail.
– Abraham Maslow, The Psychology of Science
This is the case when the popularity of the pattern, to be more precise, its implementation played a bad joke with it us. While you are building a Controller there are no clear boundaries or directions on how to keep its integrity. Heavily imposed pattern with a poor restriction model always leads to faded boundaries and loss of integrity. Due to almost no visible alternative, the MVC became a dotnet way of building stereotypical architecture, in fact, any architecture.๐ฌ
Even though it clearly says RPC in the name, it is still a communication protocol, not a pattern, it describes how two endpoints need to communicate, it does not build real expectations on how endpoint code is organized. While MVC does…
I’m mostly focusing on the Controllers, although I have the same or similar concerns about Models, ViewModels, you tell me how you name it. I just think it has less impact on the overall application architecture. The direct one-to-one mapping made it a confusing DTO with faded boundaries.
Even if MVC is a good fit for your problem domain, it requires a lot of self-discipline to keep Controllers neat and well structured. Usually, two symptoms should indicate that you are probably on the wrong path and you might need to reconsider your choice of pattern.
Too big
You saw them, the massive do-everything/know-everything Controllers that have too many entry points (Actions). The amount of parameters in the constructor is constantly growing proportionally to the number of actions. Sometimes I’m wondering, how many applications are there on-line with a single huge mega-controller? ๐ค
This is a direct result of lost integrity and faded boundaries. Controllers became trash cans.๐๏ธ You can’t tell whether something belongs there or not. Sometimes it is a single garbage-pile-controller which saves all others and keeps the rest of the application relatively clean ๐ฏ, in a way acts as an \utils
folder (if you know what I mean ๐)). Sometimes multiple controllers just grow independently and concurrently.
The context is ambiguous, cognitive load together with actions and responsibilities limits to infinity. Any change is a nightmare and almost always results in an additional constructor parameter.๐ฌ
Too small
On the other hand, the imposition comes into play and MVC Controllers are used when there’s no real need in them. In scenarios when the application needs to handle a set of independent HTTP requests. You can achieve that using one action per controller and one controller per unique HTTP Request, but it doesn’t look right. It is more like a deodorant-approach for a very distinctive architecture-smell.๐
Finally, it is almost impossible to keep it like that if you are not working alone on the project.
The time factor โ
The one might argue that we are missing on the time-factor, the MVC is an old-school pattern. Maybe it has its disadvantages, but at the time, the application requirements, hence architecture was completely different. It wasn’t built with sophisticated client-side applications in mind. Therefore maybe most of the problems are just legacy heritage plus evolving requirements? ๐ค
What about ASP.NET Core Web API? The Web API suppose to remove enforcement of the MVC pattern. It is an important step, given that front-end took a lot of burden from the back-end and MVC is useless in modern single-page applications.
However, while Web API is removing one pattern it enforces another. Unless you going to do RESTful API, you will find yourself suffering from all the problems that we just talked about. And not everything fits into REST that well, moreover I’d say that there’s relatively very low number of domains that will endorse REST architecture.
You’ve been left with more lightweight less opinionated, but hammer which means all your problems still have to be nails. ๐จ
Is it the pattern or is it something else?
So we changed the pattern and architecture just to find ourselves with the same set of problems. What does really causing all these issues? If you’ve been following along, you probably know. ๐ Controllers. To be more precise- the imposition of the Controllers for any HTTP related interaction which predominantly caused by the absence of a simple and scalable alternative. ๐ฅ
Naming
As we know each Controller must have at least one action. Controller and action names form how your URL path will look like.
There are only two hard things in Computer Science: cache invalidation and naming things.
– Phil Karlton
This would be another good symptom which will help you identify potential pattern mismatch. The naming process becomes odd and awkward.. The obligation of having Controller and Action multiplies x2 one of the hardest things in computer science.
With WebAPI you might delegate some naming issues to the HTTP Request Type (e.g. Get or Post). But -once again- that’ll work only if you are doing the RESTful API. Otherwise, you will find yourself (or your colleagues) confused when you (they) will try to consume your API on the other side (whether it is front-end or another service). Or you will still have to name things explicitly, identifying what are you trying to perform here, which will roll you back to the naming problem x2
Clearing the air a bit ๐ฌ๏ธ
Just to make things clear here. ๐ I don’t think that either MVC or RESTful APIs is bad, they do the job great. If and only if they do match your domain in all axis. And you have good self-discipline to follow the guidelines and best practices ๐
Remember. If you are building web application and you need to use HTTP, Controllers in all its variations is not the only way to go.
Alternatives?
I intuitively recognized the symptoms and stopped falling into controller-trap quite a long time ago, however, I didn’t try to identify the root cause.
After listening to one of my favorite podcasts .NET Rocks, Episode ASP.NET Core API Endpoints with Steve Smith I finally got all the puzzle pieces right and managed to explain my long-time love with the CQRS.โค๏ธ
I was exploring different alternative ways of building HTTP APIs and here is my TOP list. ๐
CQRS
I found that CQRS (Command Query Responsibility Segregation) pattern is a great way to deal with complex domains. It scales great, it takes advantage of well-structured restrictions and it perfectly lays on top of the HTTP.
Even though I like CQRS ๐ I would avoid using it everywhere and leave it for medium-high complexity domains.
Mediator
For a low complexity or highly-granular domains I’d use something RPC-ish. I found the Mediator Pattern works well here.
Hiding the smell ๐
I think the MediatR by Jimmy Bogard is another example of the tool that we perceive the pattern through. MediatR is a very clean and easy-to-use yet very powerful tool. It allows you to organize your code cleanly and properly distribute responsibilities. Check it out if you haven’t.
Anyhow often I see it used as a deodorant for the architecture-smell ๐. Which creates even more smell ๐๐. When you really don’t want to use either MVC or REST, but you don’t have enough courage to say “no” to Controller, you start doing this. โคต๏ธ
I even attended a workshop where it was introduced as a clean architecture. ๐คฆโโ๏ธ
๐จDon’t try this at home ๐จ
So here’s a recipe for the “Clean Architecture”. You’ll need a controller, with a single action. The Action needs to send a MediatR Request. The MediatR Request was delivered to the MediatR Request handler. Which finally doing something and returns the MediatR Response. The MediatR Response is transformed into the Action Response and returned by the Action back to the client.
You can see a bit of redundancy happening here.
You will find similar architectures in different variations, but now you know whom to blame ๐
HTTP Mediator
I really like the idea of organizing RPC this way, however, the implementation seemed obfuscated. So I thought maybe there’s a place for improvement.
The idea
The idea is very simple– remove the middle-man with all unnecessary redundancy, but keep (or even improve) the simplicity and versatility by improving and simplifying mapping model. Basically wire up the HTTP Requests with Handlers directly and glue them together with a highly performant (de)serialization and request/response dispatching. Seems like a pretty straight-forward plan. ๐ง
The implementation
Fortunately, at the time I had all the necessary instruments. To process and (de-)serialize HTTP request/response I used System.IO.Pipelines and System.Text.Json which together with asp.net core allow you to hook up into the Application Server HTTP Request Reading Process and efficiently asynchronously de-serialize data ๐คฏ.
For the dispatching part, โ๏ธ I used a similar approach to the one I used in the HTTP Commanding/HTTP Querying– pre-backed type-registry and type-instance-construction line.
This way I managed to remove all the third-party tools overhead. And utilized a native dotnet core functionality instead.
The result
The final result is the HTTP Mediator, which can be easily hooked into any asp application and will provide a good structure to organize your code in a neat and truly clean way.๐
For anyone who has ever used MediatR, main actors are well-known:
- Request/Response messages despatched to a single handler and provide a manageable response.
- Notifications are dispatched to multiple handlers.
The biggest difference here is that you are triggering them directly with the HTTP request avoiding the whole pipeline of unnecessary interactions.
Check out the documentation in the repo for more details. Additionally, the repository has a playground with examples and a pre-set-up application for your experiments.
Bottom Line ____
I think that both MVC and RESTful API provide a great convention-based restriction model that will help organize the code and data flow in the application if and only if the domain (with your permanent help) will be able to fit in the boundaries that these patterns are setting. Otherwise, your domain concepts will start leaking everywhere, controllers start to suffer from the ambiguity and poor integrity.
Now I can explain my love for the CQRS and particularly CQRS via HTTP. Nevertheless however CQRS might be overkill, particularly around domains with low-complexity or high granularity. Anything that falls in that bucket will work well with the HTTP Mediator.
All these middleware-libraries HTTP Commanding, HTTP Querying and HTTP Mediator are driven by the same purpose– “Bring the application code closer to the HTTP without exposing the last one”. Additionally, they enforce healthy restriction models that should enrich and improve the development experience. On top of it, they don’t stop you from using either MVC or WebAPI as it is just a middleware.
Disregards whether you will go with RESTful API or HTTP Mediator, always remind yourself that there’s no silver bullet. Make sure you are using the right tool for the task. ๐จ๐ฉ๐ง