The Contentious Controller
I laughed to myself when I realised how I conceptualise controllers. I went from a fresh-faced beginner who lumped all their logic in there to a clean code aficionado who took SRP out of context and had an abstraction for almost every method.
But as I’ve learned about keeping things simple from the likes of Greg Young and Udi Dahan, I’ve used this approach to systematically challenge the complexity of my code. Recently I came back to “controllers” and classified my thoughts.
I’m now going to suggest that abstractions are un-needed extra work in simple CRUD or Q&M (query and map) scenarios. I’m not saying this is the correct approach, just exploring the potential for increased simplicity.
Simple Systems Need Simple Code — Not Abstractions
If you’ve got a web app where all of the “controllers” share
this level simplicity (fetch some data and map it):
public class ShowEndpoint
{
private readonly IDocumentSession session;
public ShowEndpoint(IDocumentSession session)
{
this.session = session;
}
public CarsViewModel Get(CarsRequestModel requestModel)
{
var cars = GetCars(requestModel);
var model = new CarsViewModel();
Map(cars, model);
return model;
}
private IQueryable<Car> GetCars(CarsRequestModel requestModel)
{
return session
.Query<Car>()
.Where(c => c.Price < requestModel.MinPrice)
.Where(c => c.Price > requestModel.MaxPrice)
.Take(requestModel.PageSize);
}
private void Map(IQueryable<Car> cars, CarsViewModel model)
{
var carDtos = new List<ShowCarDto>();
foreach (var car in cars)
{
var dto = new ShowCarDto
{
Model = car.Model,
Manufacturer = car.Manufacturer,
Price = car.Price,
NumberOfDoors = car.NumberOfDoors
};
carDtos.Add(dto);
}
model.Cars = carDtos;
}
}
Do you go with common wisdom and let TDD drive out a data-access abstraction, a mapper abstraction, and some inheritance (with subsequent coupling)? You may be pressurised by what other people may think of your “SRP violation”, but you decide to be objective…..
How Does this Bloody Thing Work?
You think about the next dev who will join your team and work out what your system does. You shouldn’t really, but you admit to yourself: she will have difficulty not understanding those 2 simple methods.
Adding New Features?
You think to yourself: once I create that repository method, and other people start calling it for similar queries, the coupling will make it difficult for me to update the query in just this one place — where for
this screen only I just want to exclude cars not in this user’s region.
Fixing Bugs
One last thought flows between your ears as your prepare to knock-up a repository of type t where t is some instance of god object: if things break on the live system, and the business people are going insane — I’m going to need to find out what is broken. No, it would be too simple to know that one endpoint is broken, and the few scraps of functionality all live in that one
class.
When the Complexity Does Come?
Well, sir, you will be in prime position to know how the components of your system interact. Which use cases are most susceptible to change and which code is the most volatile — you can decompose your “nasty” controller logic according to how your system is being used.
It’s Easier and Quicker
Errr, what he said ^^^
SRP is About Users of the System
Me and my SRP violations, eh? According to Uncle Bob SRP is centric to users of the system. A responsibility belongs to a single user — and these things should be grouped together so responsibilities can be modified without impacting others.
For starters, if you’ve got a user of your system who is in any way tied to how you map from one object to another, then you’ve got bigger problems than controller logic. And you provide the LOLs for my insightful
readers.
Secondly, whilst database concerns might be an additional responsibility — a database abstraction can have very high costs. As Uncle Bob says “Welcome to software engineering”. You have to choose between trade offs.
Vertical Slices of Functionality
Importantly you’ve created a vertical slice of functionality — everything related to this one simple view of the application’s UI is in one place. You’ve grouped things together that change together. High fives!!!!!
Not All Logic Lives in the Controller
As I tripped over myself to point out, I’m talking about simple scenarios such as crud or query and map. I am not in any way suggesting domain logic or business rules live in the controller. I’m with Ian Cooper — http://codebetter.com/iancooper/2008/12/03/the-fat-controller/
In The End……..
I’m probably talking rubbish, but I did manage to find an example using the simple approach on a simple app: https://github.com/ayende/RaccoonBlog/blob/master/RaccoonBlog.Web/Controllers/PostsController.cs
There’s no correct answer, but if your current solution is working and you’re happy then life is good. I hope you enjoyed my alternative take on controller logic and my challenge to needless complexity.