Tony Marston's Blog About software development, PHP and OOP

Fat Model, Skinny Controller

Posted on 25th July 2021 by Tony Marston
Introduction
My Implementation
Design Decisions behind my Implementation
Other Implementations
Conclusion
References
Comments

Introduction

Ever since I published The Model-View-Controller (MVC) Design Pattern for PHP which explains how I implemented this popular design pattern in my open source framework I have been told by numerous "experts" that my understanding of this design pattern is totally wrong, my implementation is totally wrong, everything I do is wrong, et cetera, et cetera, ad infinitum, ad nauseam, yada yada yada, blah blah blah.

I ignore all claims that my methods are wrong for one simple reason - they work! Anyone with more than two brain cells to rub together will be able to tell you that something that works cannot be wrong just as something that does not work cannot be right. Not only do my methods work, but they produce results which are more cost effective than any of the alternative implementations that have been thrust under my nose. If you think that this claim is bogus and your methodology is better then I suggest you prove it by taking this challenge. If you cannot do this in five minutes or less, and without writing any code, then you have failed.

Why are there so many alternative opinions? It is not just my implementation of MVC which is questioned, if you care to search the internet for opinions on any aspect of OO programming, design patterns or so-called "best practices" you will find that everyone (and his dog, apparently) has a different opinion. Almost to a man everyone thinks that their opinion is the only one which has been sanctioned by the programming gods and that anything different should be treated as heresy and their practitioners should be burned at the stake.

The idea that there is only one version of "best practices" which should be allowed to exist will never work. What certain individuals describe as "best practices" which the entire programming community should follow is nothing more than "common practices" among their particular group, and different groups have their own idea of what is best for them. If any one group tries to force their opinions on another group then they will quickly hit a wall of opposition.

My Implementation

I never set out to implement the MVC design pattern in my framework at all. By this I mean that I did not read about it and then decide to implement it, I simply wrote the code in my usual inimitable style, and one day a colleague pointed out that what I had produced contained three components which carried out the responsibilities of the Model, the View and the Controller. What I started to build was based on the 3 Tier Architecture which I had encountered and successfully implemented in my previous language, but what I did was to split the Presentation layer into two parts by creating a single module which put the data into an XML file and then transformed it into HTML using XSL stylesheets. This became the View in my framework, and what was left over became the Controller. Unlike standard MVC the 3 Tier Architecture has a separate component for all data access, and this effectively split the Model into two parts, as shown in this diagram.

I often tell people that I implemented the MVC design pattern by accident, not by design.

Because I started by building my framework according to the rules of the 3 Tier Architecture it was quite obvious to me all business rules should be processed in, and only in, the Model. The idea that I should validate user data in the Controller before I inserted it into the Model just did not exist in my universe. I have a separate object for each database table, and I inject data into one of these objects using a standard insertRecord() method which validates that data BEFORE it is written to the database. If this data fails any validation checks then it is returned to the user with suitable error messages and it is NOT written to the database.

My Controllers are "skinny" by virtue of the fact that they DO NOT contain any business logic, any data access logic, or any view logic. All they do is receive requests from the user, translate those requests into one or method calls on one or more objects, and when those objects have finished their processing they are handed to the view object which extracts the raw data (which at this point is nothing more than a PHP array) and then transforms it into a document which is readable by the user. The document format could be HTML, PDF or CSV.

My Models are "fat" by virtue of the fact that they contain all the business rules which can be applied to the data for which they are responsible. Each Model is responsible for a single table in the database, but does not contain any logic which communicates with the physical database as this is handled by a separate Data Access Object. The idea of putting the business rules into separate objects did not occur to me as it violated two fundamental principles of OOP:

There are other features of my implementation which I have not seen anywhere else which would therefore make them totally unique:

While teaching myself PHP from various books and articles on the internet I saw the hand-crafted HTML being output in small chunks as the script progressed through its processing. While this may be OK for small and simple programs I knew that the applications which I would eventually be building would benefit from something more sophisticated, so I looked for an alternative approach. I had already encountered XML and XSL in my previous language and was impressed with its power and flexibility, and after verifying that the necessary processing was available in PHP I decided to use these in my templating engine. While I did start off with a separate XSL stylesheet for each web page, after a period of refactoring I managed to create a small collection of reusable XSL stylesheets which could be used to generate any number of web pages. This meant that I could create a single View object which could extract the data from one or more Model classes and transform it into a web page whose structure was defined in one of my 12 XSL stylesheets. This method also became useful when it was decided to convert the GM-X application to provide responsive web pages. All 2,700+ screens were converted in one man-month, as documented in GM-X: The World's First Mobile-First ERP.

Design Decisions behind my Implementation

All this was made possible because of certain design decisions which I made at the start of my PHP development. I have subsequently been told by several OO "experts" that these design decisions were totally wrong, but as these decisions have produced nothing but excellent results I can only conclude that these "experts" are either barking up the wrong tree or are barking mad.

These design decisions were influenced by the following observations:

  1. When writing an application which communicates with objects in a database instead of objects in the real world, each Model should be modelled around the database object (table) and not the real world object which the table represents. This is why I have a separate class for each database table instead of classes which handle groups of tables which, in my opinion, would break the Single Responsibility Principle.
  2. Regardless of the different types of data which they may hold, each and every database table is subject to exactly the same set of CRUD operations. I ensure that every table class includes the standard boilerplate code for these operations by defining them as methods in an abstract table class which is then inherited by every concrete table class.
  3. While all the tutorials I had seen followed the idea that each column in a database table should have its own property in that table's class, with it own getter and setter, I decided that this was too cumbersome. I had already noticed that when data is sent to a PHP script that it appears in the form of an associative array called $_POST, and that when calling mysqli_fetch_assoc after a SELECT query the data is also presented as an associative array. So if both the Presentation layer and the Data Access layer could handle their data in a single array I saw absolutely no reason why the Business layer should not follow suit. This means, for example, that when I want to insert a new record into a database table I can do it in the Controller with a single call - $result = $dbobject->insertRecord($_POST). Every subsequent method in the Model, which is inherited from the abstract table class, has this same array as its first argument, so I never have to unpack this array into its component parts only to repack it later on. This is why my single View object can extract the data from any Model in the application without knowledge of any column names.
  4. Every task (user transaction or use case), regardless of the end result for the user, boils down to nothing more than performing one or more of the standard CRUD operations on one or more database tables interspersed with methods which process the different business rules. Each Page Controller performs a fixed number of operations on a fixed number of Models, but it does not contain any hard-coded references to identify which Models. This is performed by a small component script for each task which identifies which Controller and which Model(s) need to be combined in order to carry out that particular task.
  5. After having personally written hundreds of different tasks for numerous applications with their different databases I began to see repeating patterns based on the task's structure, behaviour and content. This enabled me to build a collection of components which handled different combinations of structure and behaviour where the different content could be identified at runtime. These reusable components are described in Transaction Patterns for Web Applications. The ability to link a Transaction Pattern with a database table to provide a working Task is provided in my Data Dictionary.
  6. Because the framework provides large amounts of boilerplate code in its collection of pre-written and sharable components it is extremely easy to create sets of working transactions for each database table which instantly allow application data to be inserted, viewed, updated and deleted. Standard validation provided by the framework ensures that only valid data can be written to the database. Non-standard business rules can be added to each table class later using the pre-defined "hook" methods.
  7. Because every Controller communicates with its Model(s) using common methods which are inherited from an abstract class I have been able to convert each of these methods into a Template Method which then allows each table subclass to contain nothing but "hook" methods where the non-standard business rules can be defined.

I do not follow the instructions contained in Domain Driven Design (DDD) where it says that every use case should have its own method in a Model as this would completely remove the ability of my framework to have such large amounts of reusable code. Instead each use case has its own entry on the MNU_TASK table which then provides the following abilities:

It should be obvious to any competent programmer who understands OO theory that having a huge number of unique non-sharable methods immediately removes any opportunities for polymorphism, without which you cannot have Dependency Injection (DI). If you do not have a choice of dependencies which you can inject into an object then you cannot reuse the functionality provided by that object. Thus instead of having 40 pre-written and reusable Page Controllers I would need to write thousands of custom controllers to deal with each of those unique methods.

Other Implementations

While the description of the MVC pattern clearly identifies three separate elements - the Model, The View and the Controller - and identifies the responsibilities that should be carried out by each, there are continuous and often heated debates about what pieces of logic (program code) should go where. This debate often boils down to three different options:

  1. Fat Models, Skinny Controllers
  2. Skinny Models, Fat Controllers
  3. Skinny Models, Skinny Controllers, Fat Services

As you should already be aware I favour the first option where the Controller does not contain any business logic, data access logic or view logic as these are handled by totally separate components. All the Controller does is as follows:

I have performed a quick search on the internet on this topic and found numerous different opinions, some of which are listed below along with my reasons for either liking or disliking them.

  1. In Chapter 12. Fat Model, Skinny Controller it makes some observations with which I fully agree, such as:
    Up to this point, the examples in this book have been applying the opposite of a fat model, skinny controller, which is a fat controller, skinny model. The term "fat" implies the presence of business logic; "skinny" implies the lack thereof.
    It also states the following:
    No matter which approach you take, the end goal of the fat model is to place all of your business logic in the M of MVC. The M should be able to stand alone as a complete application (without a user interface). The V and C that interact to make it MVC can be a console application, a RESTful API, a web application, etc. It shouldn't matter to the M.
    This to me is so blindingly obvious I simply do not understand why other people struggle with it. Each Model can be accessed by any number of Controllers, each for a different use case, and it is vitally important that every use case has access to the same business logic for the same Model. The only way to absolutely guarantee that different Controllers which act upon the same Model has access to the same business logic is to put that logic in the Model and NOT to duplicate it across several Controllers.
  2. In Phase 3: Skinny Controller, Skinny Model the author states the following:
    What is the single responsibility of models? Is it to hold business logic? Is it to hold non-response-related logic?

    No. Its responsibility is to handle the persistence layer and its abstraction.

    Business logic, as well as any non-response-related logic and non-persistence-related logic, should go in domain objects.

    I'm afraid he has this backwards. Models are the same as domain objects as they hold all the business logic for the domain. All business logic resides in the Business layer while data access logic resides in the Data Access layer. The names "business layer" and "data access layer" already contain hints as to the type of logic which each layer should contain, so there should be no excuse for this level of confusion.
  3. Some people favour having a single Controller for each Model which can then handle all the use cases for that Model. This means that you create a custom Controller for each Model where that Controller can only be used with that Model, and that Model can only be accessed by that Controller. This is a prime example of tight coupling which should be avoided. Loose coupling is supposed to be better, and I achieve this by having a standard collection of pre-written Controllers which can be used with any Model. I do not have a single Controller which can handle a choice of output formats - I have one which responds with HTML output, another for PDF and a third for CSV.
  4. Some people say that a Controller can only ever communicate with a single Model, but I have never seen such a rule which is why I don't follow it. This to me is wrong as it encourages the creation of Models which are responsible for more than one database table, and the words "more than one" should immediately indicate to you that such a Model would be violating the Single Responsibility Principle. While most of my Controllers deal with a single Model there are some which handle two, three or even four.
  5. Other programmers choose to have separate public methods called load(), validate() and store(). This is not a good idea as it allows for more data to be inserted after the validate() has been performed, which could lead to errors during the store(). In my framework I do not treat these as separate operations as they must always be executed together and in a particular sequence. In other words they form a group operation in which they are separate steps within that operation. If you look at either insertRecord() or updateRecord() the load() is performed by passing all the data in as an input argument while the validate() and store() are performed internally. Note that the store() method is only called if the validate() method does not detect any errors. For fans of design patterns this is an example of the Template Method Pattern where the abstract class contains all the invariant methods and allows variable/customisable methods to be defined within individual subclasses.
  6. Some people allow custom methods to be added to Controllers, but I do not. A Controller can only call those public methods which have been defined in the abstract table class. Any custom logic can be handled by inserting code into any of the customisable "hook" methods. All of these methods are defined within the abstract class and will be called at certain points within the processing flow. It is possible for the developer to put code into one of these methods which calls a method which is unknown to the framework, but that then becomes the responsibility of the developer.
  7. Some people say that if you put ALL your business logic into a Model then you could end up with extremely fat models. They suggest moving this logic into a collection of smaller service objects. I do not like this idea. When I am looking for a piece of logic associated with an entity I find it easier to look in the single class which is responsible for that entity rather than a group of classes which only deal with bits of that entity. It is easier to search through a single class file than it is to search through multiple files. The fact that a Model may contain 1,000 lines of code to deal with its business logic is irrelevant. If you take that 1,000 lines of code and spread it across a bunch of smaller classes you still have 1,000 lines of code, but now its in fragments in multiple places. On top of that you now have MORE lines of code just to call the right method in the right class.
  8. In "Fat model, skinny controller" is a load of rubbish the author states the following:
    I do believe that it's better to have a fat model than a fat controller, as controllers are notoriously difficult to test and even more difficult to DRY up ("don't repeat yourself"). But the problem with this approach is that, as your application grows, it will end up with god objects - huge, monolithic models with thousands of lines that are very difficult to maintain.
    I disagree with the idea that as your application grows the Models get fatter and fatter. I have a separate Model class for each table in the database, and as my application grows I add more database tables, and create a new Model for each new table. I do not have to amend any existing Models, Controllers, Views or Data Access Objects.

    There are times when a particular Model requires several different tasks which require different sets of custom code, and sometimes this custom code would like to occupy the same "hook" method as another task. I solve this problem by creating a subclass of the Model where the base class contains the code for the common tasks and the subclass contains only that code which is required by a specialist task. By "base class" and "subclass" I mean the following:

  9. In responses to "Fat model, skinny controller" is a load of rubbish on Reddit a poster said:
    The author misunderstood "model" in MVC. It's a layer, not a class.
    Where on earth did this idea come from? None of the articles which I have read concerning MVC have ever said that any of those three objects is anything other than a single object. While some individuals may choose to split any of those objects into smaller parts it is an option and not a requirement. The fact that in my own implementation I have put the processing of primary validation into a separate class is my own personal choice and not because I was following the "advice" of some gormless guru.

    The only way I can justify referring to the Model as being a layer and not just a single class or object is when all database access is stripped out of the Model and placed in a separate Data Access Object (DAO). In my own framework I have a different version of this object for each supported DBMS, which then allows me to switch my application from one DBMS to another simply by changing one line of code in my config file.

    Some people seem to think that by putting ALL the business rules in a Model would make them too fat, so they suggest splitting out all this logic into collections of different service objects. This violates the primary purpose of encapsulation which states that ALL the data and ALL the methods which operate on that data should go into a SINGLE class. If you separate them then you are creating anemic domain models which are considered to be an anti-pattern.

  10. In this comment on the same Reddit post a different person said:
    MVC is a USER INTERFACE design pattern, not an application or architectural design pattern.
  11. This is another strange idea that has been pulled out of thin air. According to the Gang of Four, who wrote the first major book on software design patterns, there are only three categories - creational, structural and behavioural. This book does not describe the Model, the View and the Controller as distinct patterns in their own right, instead it says that they can be constructed by mixing patterns such as Factory (creational), Composite (structural), Observer (behavioural) and Strategy (behavioural). That book does not have UI patterns as a category, so what makes you think that it is a valid category? What are your sources?

    In the same comment he said:

    The M in MVC is the Model that holds the data for the VIEW, not the application data that you store in a database!
    I disagree most strongly. In any software application the data does not simply exist in the Model waiting to be displayed in the View, it is persisted in a database so that it can be maintained over a period of time, which could be many years. That data is also subject to different sets of business rules during the input process and also the output process, and it is the Model which also contains these business rules. It is the Model which is at the heart of every application. The data which it holds is written to and read from the database, and it is also read from and written to the user interface.

Conclusion

Consider the following statement:

Some people know only what they have been taught while others know what they have learned.

When I made the transition to an Object Oriented language (PHP) after 20 years of working with COBOL (procedural) and UNIFACE (model driven, component based) I already had vast experience with designing and building database applications. I had been taught different ways of doing things, some better than others, and I learned to separate the wheat from the chaff. When I came across a different idea I learned to evaluate it to find out whether it led me down the path to higher productivity or the road to hell. Working for a software house where I worked on one new project after another for different clients the primary consideration was cost-effectiveness not code purity. The former is easy to measure while the latter is purely subjective as different people have totally different ideas as to what "purity" and "perfection" actually mean.

I created a set of personal development guidelines which were later adopted as the company standards. I developed a framework in COBOL which immediately made every developer in the company more productive. I rewrote that framework in UNIFACE when the company switched to that language. When I switched to using PHP as a self-employed contractor I rewrote that framework for a second time. With each of these rewrites I found that I could use the features of the new language to become even more productive. For example, in order to develop a common family of forms the amount of time taken was 5 (five) days in COBOL, 1 (one) day in UNIFACE and 5 minutes (yes, FIVE MINUTES) in PHP. So when other developers have the audacity to tell me that my methods are wrong, even though their levels of productivity are way below mine, is it any wonder that I question their competence?

Instead of going on courses to be taught the "right" way to implement the concepts of Object Oriented Programming I taught myself using just three sources: the PHP manual, online tutorials, and various books. I had no idea at that time that I was not following the rules of Object Oriented Design (OOD), Domain Driven Design (DDD), Design Patterns and the SOLID principles. I did not follow these rules simply because I did not know that they existed, and after rewriting my framework to take advantage of these new concepts called Encapsulation, Inheritance and Polymorphism I saw that it made me more productive than ever before. When people started to tell me that I should refactor what I had done in order to follow their version of "best practices" I refused to do so for one simple reason - it would have ruined what I had done and replaced simple code with vast swathes of complicated and inefficient code and obliterated all my gains in productivity. That did not strike me as a good idea.

Every time you ask a different person how to implement MVC you will get a different answer, and different reasons why one person's answer is superior to another's. In reality there is no such thing as a single definitive answer which is 100% right for 100% of the population, so all you can really do is follow the answer with which you feel the most comfortable and which gives you the best results. Sometimes this means trying different approaches so that you can compare the results and reach your own conclusions instead of picking one answer out of a hat and running with it regardless of the consequences, only to discover at a later date that the option you chose was not actually the best.

Anyone who follows a rule simply because it was the only one which they were taught is in great danger of becoming nothing more than a Cargo Cult Programmer, a Copycat or a Code Monkey. Such people are incapable of having an original thought as they rely on other people to do the thinking for them. They will never be leaders, only followers. They will never be innovators, only immitators. A good developer should not be afraid to question every rule, to question why it should be followed, to compare it with the alternatives and to then make up their own minds as to which is actually best for them. If you can achieve better results by not following a particular rule then how can that rule be justified? Following a bad rule just to be consistent will do nothing but make every follower consistently bad.

I consider my implementation to be more "correct" than the alternatives simply because it allows me to create and maintain application components at great speed. My ERP application currently covers over 16 business domains, contains 450+ database tables, 1,200+ relationships and 4,000+ tasks (use cases). As the application was built using a combination of the 3 Tier Architecture and MVC design patterns, as shown in this diagram, it is comprised of the following groups of components:

These components can either be regarded as services or entities.

Note the following:

You may think that my methods are wrong, but unless you can prove that your methods are better by taking this challenge and producing results of the same quality in the same (or less) time then I shall continue to ignore you.

Here endeth the lesson. Don't applaud, just throw money.


References

The following articles describe aspects of my framework:

The following articles express my heretical views on the topic of OOP:

These are reasons why I consider some ideas on how to do OOP "properly" to be complete rubbish:

Here are my views on changes to the PHP language and Backwards Compatibility:

The following are responses to criticisms of my methods:

Here are some miscellaneous articles:


counter