Taylor Otwell: “Thin” Controllers, “Fat” Models Approach

I was listening to recent podcast by Taylor Otwell, Laravel Snippet episode 11, where he touched on the debate in Laravel community on where you should put your code logic – in Controllers, Models, or Services. Here’s what he said.

First, I’ll just quote Taylor, and then provide some comments and additional links below. So, “skinny” or “fat” models? What approach does creator of Laravel prefer?

I tend to prefer a “fat” Model approach. My Controllers are actually pretty skinny, I find.

I think DHH (David Heinemeier Hansson), who created Ruby on Rails, had a blog post years ago about how he likes to use only the “resource” verbs on Controllers and that he doesn’t like any other actions. So, when you have a Controller, you limit yourself to only index/show/store/update/delete, and if you need any other actions – usually that’s an indicator that you can extract the whole other Controller that follows those resource verbs conventions.

So I follow that pretty strictly, which, I find, makes my Controllers pretty thin, and then I would have pretty beefy Models that have pretty rich logic on them.

I think one misconception of this approach is that just because your Model has quite a bit of logic exposed in its public API, that doesn’t mean that all the code to perform those tasks has to be within the Model.

So, take Forge for example, just assume a hypothetical Site Model, that has a deploy() method. Of course, sticking the entirety of the deployment logic within the Site Model is probably not even possible, for starters, and probably wouldn’t be a good idea, cause it would be quite a bit of logic. But, what you could do, is still have that public method on the Site Model, but have that call another method, or another Service, or fire off some Command or Queue Job, to actually perform the task.

So, the Model is not necessarily “fat” in terms of lines of code, but it just has a rich API of public methods that give you a fluent nice interface, that is very readable. It’s very readable to have Site deploy(), even if behind the scenes it’s firing off other things. It keeps the public API really nice.

It reminds me of Adam Wathan’s talk at Laracon US 2017, called “CRUDdy By Design”:

Personally, I like this approach but don’t follow it strictly, so if I have one custom action it feels weird to create a separate full Controller for it. So it depends. Also, there was a Reddit discussion with 52 comments on this approach.

What I do like, though, from Taylor’s opinion, is to have custom methods in Models. For a long time, there was a debate in community, that Eloquent Models need to have only Eloquent stuff – settings like $table or $fillables, methods for Relationships, Accessors/Mutators, and nothing more.
But if the creator of the framework himself says that you can put whatever you want there, as long as it doesn’t include all the logic, I agree it’s more readable than calling Service/Job directly from Controller.

What do you think? Agree with Taylor? How “thin” are your Controllers and Models?

You can listen to the original Laravel Snippet #11 podcast here, this topic starts at around 6:20 minute.

Like our articles?
Check out our Laravel online courses!

7 COMMENTS

  1. Hi, great post.

    I prefer to use fat models using the language of the domain, this way it is very easy to see what the models do in the controller. If the business logic is complex or I want to separate a little the things, I use services.

  2. I have been an avid follower of the thin-controllers-fat-model approach ever since I switched from 20+ years of writing database applications in non-OO languages to an OO language in 2003. My previous language introduced me to the 3-Tier Architecture, with its separate Presentation (UI), Business and Data Access layers, and I liked it so much I used that as the starting point when I rewrote my development framework in PHP. I had also become familiar with the idea of building HTML documents using XML and XSL transformations, which meant that I ended up with two components – a Controller and a View – in the Presentation layer, with the Model (minus all database access) in the Business layer.

    While I have a separate Model for each database table I do not have a single Controller for each Model. I noticed decades ago that each user transaction (aka use case) will perform one or more operations on one or more tables, and as there are only four operations which can be preformed on a table – Create, Read, Update and Delete (CRUD) – I built these four methods into each table class, then built Controllers to call different combinations of these methods on an unknown table class. I say unknown because none my Controllers contains a hard-coded table name. It is not until runtime that the table’s class name is passed into the Controller using dependency injection.

    I can guarantee that each Model contains the necessary methods as they are inherited from an abstract table class. This then gives me the ability to use the Template Method pattern for each database operation as I am then able to define a series of steps containing a mixture of invariant and variant methods. The invariant methods contain standard boilerplate code while the empty variant methods, also known as “hook” methods, can be defined in individual subclasses to contain whatever custom logic is desired.

    Each Controller performs a single use case based on what I call a Transaction Pattern, which is a pattern of behaviour which can be performed on any database table. Some of these patterns form a family as they need to work together. The most common “family” is comprised of a LIST1, ADD1, ENQUIRE1, UPDATE1, DELETE1 and SEARCH1 where the LIST1 shows multiple database rows while the others show only one row at a time. The LIST1 is run from a menu button while the others are run from navigation buttons in the LIST1 screen.

    You might think that having only 6 Controllers for 6 Transaction Patterns is rather limited, and you would be right. That is why my current library has 45 different Patterns and Controllers to cater for all the different situations that I have encountered in my long career. I have used my framework to build a large ERP application which currently contains over 3,000 user transactions, some of which are simple while others quite complex, which proves that it is a workable approach. All the standard boilerplate code is defined in the abstract table class which each Model class need only supply that logic which is specific to its database table.

    Having thin controllers is one thing, but having thin controllers which are reusable is even better. The fact that each of my 45 Controllers can talk to any Model using methods that each Model class inherits from an abstract table class makes maximum use of this feature of OOP called polymorphism. If I have 45 Controllers which can talk to any of my 400 Models then it means that I have 45 x 400 = 18,000 (yes EIGHTEEN THOUSAND) opportunities for polymorphism. I could not achieve this with fat controllers, which is why I have good reason to avoid them.

      • If that impresses you then I have more.

        I do not have to create any of my Model classes by hand as I can get my framework to generate them for me. As each Model represents a single database table I have built a small Data Dictionary application, which is built into my open source framework, so that I can import a table’s structure into this database at the press of a button. By pressing another button I can then export this information to my application as a pair of PHP scripts – one is the table class file while the other in the table structure file. Why two files? So that if the table class is amended to include any “hook” methods these will not be overwritten when the table’s structure is updated. I can amend a table’s structure at any time then re-import those changes into my Data Dictionary and re-export them to my application without having to manually amend any scripts.

        And there’s more. Each user transaction is now an implementation of one of the 45 Transaction Patterns and one or more Model classes, so I have extended my Data Dictionary to generate these user transactions for me. I simply go into my Data Dictionary, select a table, press a button to “Generate PHP”, select which Transaction Pattern I want, then press a second button to generate all the necessary scripts. If I choose a LIST1 pattern this will also include all the other patterns which belong in that family. It will also add the necessary entries to the MENU database as follows:
        • Create a separate PHP component script for each of the 6 tasks.
        • Add each of the 6 tasks to the MNU_TASK table in the MENU database
        • Add the LIST1 task to the MNU_MENU table so that it will appear on a user menu.
        • Add the 5 other tasks to the MNU_NAV_BUTTON table so that they will appear as navigation buttons when the LIST1 task is active.

        Due to the Role Based Access Control logic which is built into my framework I then have complete control over the number of tasks which an individual user is allowed to access. If they have not been granted access to a task then it will not appear in any menu or navigation button.

        The great benefit of all this is that after adding a new table to my database I can import the table’s structure into my Data Dictionary, generate the class file for that table, then generate a family of tasks, which are immediately runnable, to view and maintain the contents of that table, all by pressing a few buttons. This can be done in 5 minutes without the need to write any code whatsoever – no PHP, no HTML, no SQL. The standard code will automatically validate all user input to ensure that it matches the database specification, so all the developer has to do is update any of the hook methods in the table class to implement any additional business logic.

        Adding new tables to my database, or even changing an existing table’s structure, is now a simple exercise. By having a lot of boilerplate code embedded in my reusable Controllers and my abstract table class this has also given me the ability to extend that logic without having to amend any of my Model classes. I have been able to extend the functionality within a Controller, or even create new Controllers for new Transaction Patterns without having to amend any other components. As I write nothing but database applications for businesses this technique makes me extremely productive as the framework takes care of the standard stuff and all I have to do is add in the unique stuff.

        • Wow, impressed again. As creator of code generator QuickAdminPanel.com, I’m really interested in your framework, maybe will get some ideas from there. Do you have it published somewhere by any chance, or you use it internally? How many successful projects did you create with it? Did it stand the test of time, of Laravel/PHP changing?

  3. My controllers only do validations, and call business layers, that contains all the logic.
    This business layer calls “services” layer that are directly in relation with eloquent model (or other).
    I considere eloquent class as a part of the model, and as DAL, i prefere no write logic into them.

LEAVE A REPLY

Please enter your comment!
Please enter your name here