Model-View-Controller (or MVC for short) is one of the most misunderstood design patterns in software design. MVC has its origins in the SmallTalk community in the late 70s but it was only in 1988 that it was expressed as general concept in an article by Glenn E. Krasner and Stephen T. Pope for The Journal of Object Technology.
The main idea behind MVC was to keep the presentation and business logic decoupled from each other.
Up until mid 90s, MVC had mainly been used for desktop and embedded applications. Three-tier architecture (presentation, logic, data) was the reigning architectural style in the 90s, and MVC fitted as a glove. In the late 90s and early 2000s, most companies started migrating their applications to the web and the need to keep the user interface separate from the business logic became even more important. The MVC pattern promotes exactly that and it was a natural step to use MVC for web applications. However, we now had a browser, http calls, and the View would be rendered outside our applications, separately from our Controllers and Model. In order to make things easier for developers, a few frameworks were created. In the Java world, we had MVC Model 1 and Model 2 with Java Server Pages (JSP) and Servlets. Struts, the first major Java MVC framework, was created in the early 2000s. From then onwards, we had many attempts to make the communication between the browser (View) and our applications (Controller and Model) seamlessly. Many other frameworks like Tapestry, Java Server Faces (JSF)and Spring MVC emerged in the Java world. Other languages and platforms also created their own, Rails being one of the most successful ones.
As web applications and frameworks diversified, the MVC pattern also evolved and new variations like MVA, MVP, MVVM and PAC emerged. MVC frameworks became popular and as part of their evolution they tried to automate more and more the manual work the developer had to do, including database access. MVC frameworks started automating how Entities are persisted using Object-Relational Mapping - ORM techniques or closely integrated with other ORM frameworks. Due to the simplicity of many web applications, MVC frameworks made it easy to capture data in a web form and store in relational databases. They also made it easy to read data from the database and display on the browser. The price for this automation was a bastardisation of the Model layer, which became synonymous of Entities that represented tables in the database.
From the original MVC idea, the Controller layer should be a very thin layer, handling the requests from the View, delegating all the business behaviour to the Model. Depending on the result returned by the Model, the Controller decides which View to display next.
In the early days of the web, most MVC frameworks provided a way to generate the View on the backend (server). This was done using some scripting language or template engine. In the Java world we had things like JSP, FreeMarker, Mustache, or Jade. For each request, the server would generate the whole web page and send the full HTML code back to the browser. The UI was quite dumb and the backend was normally stateful, with MVC frameworks keeping track of the HTTP Sessions. This is how the MVC looked liked:
Fully Coupled Server-side MVC
Decoupled View MVC
But that was not enough. We still had problems. The full application had to be redeployed if we wanted to make a change either on the front-end or backend. Scaling stateful backends was expensive. We also had to deploy our applications (at least in the Java world) inside web or application servers. In order to solve that, we had to completely decouple the front-end and backend and ideally, have a completely stateless backend. Front-end technologies like AngularJS and Node.JS allowed us to move both View and Controller to the browser. With this, we could create stateless backend, only providing APIs.
Decoupled Model MVC
Although we changed were the View and Controller lived over time, where does MVC fit when it comes to the wider architecture of an application?
In Domain Driven Design(DDD) applications are normally split into 4 layers:
Note: I’ll leave the discussion around Layered and Hexagonal Architecture for a different blog post. For now, let’s just focus on separating concerns and keeping our domain model isolated.
Behaviour and state are both part of the domain model of an application. The domain model is a set of business concepts defined by many different design elements or building blocks. As defined in Domain Driven Design, these design elements are not only Entities but also Services (Application, Domain, Infrastructure), Repositories, Factories, Aggregates, and Value Objects.
MVC is a macro pattern that can be used as a good guideline to keep the delivery mechanism decoupled from the domain model. With that in mind, if we superimpose the delivery mechanism, domain model on the MVC structure we will have the following:
When it comes to MVC frameworks, they should be restricted to the View and Controller layers, never the Model.
Many web frameworks allow us to transform entities into JSON/XML and vice-versa. Some do that via annotations, others do it via reflection or name convention. It is also a common practice to use ORM frameworks to automatically persist and retrieve data from a database using our entities. Similar to the MVC frameworks, we need to add ORM annotations to our entities, or rely on reflection or name convention. With annotations we explicitly make our Model layer to know about the Controller and persistency, causing a circular dependency. If we rely on naming convention or reflection, we have a worse problem because the coupling is still there but is not visible. If we change an Entity, we may not know that we are changing the API or Database.
Although reducing boiler plate code using frameworks sounds a great idea, we are coupling the structure of our entities (domain model) to the APIs we provide to the external world and our database. Changing the database impacts the API and vice-versa. What had initially been seen as a time saver, now is a reason to avoid change. The larger and more coupled the system grows, the less developers will be inclined to make changes due to the size of the ripple effects. Many web apps today have APIs which are far from ideal as they reflect the internal data structure of the application instead of focusing only on the information that would make more sense to the external world.
Do not be afraid to create your own mappers. They are normally very simple to write and there are many small libraries specialised to parse objects to JSON or persist data to databases. The advantage of writing our own mappers is that we do not need to couple our APIs or databases to anything. Changes are localised and easy to change. On top of that, we can easily test-drive our mappers and move API tests to the unit level instead of doing it at Acceptance level.
The biggest advantage of keeping the Domain Model decoupled from the Delivery Mechanism is that it give us deployment options as the application evolves.
While the application is small, we can keep the separation between delivery mechanism and domain model just using good package/namespace structure or sub-modules, but still keep everything in the same project and deploy both together, as a single application.
Embedded Domain Model
As the application grows and we want to add different delivery mechanisms, or use different technology stacks, or scale delivery mechanism and domain model in different ways, it makes sense to deploy them individually. If the separation is already there, it would not be so hard to achieve that. We just need to wrap the Domain Model in some infrastructure and change the invocation from the Delivery Mechanism.
Deployable Domain Model
In MVC, Controllers are very thin classes (just a few lines) which identify the request from the user, invoke the appropriate behaviour in the Model, and invoke the appropriate View once the Model returns. Controllers might also need to deal with code related to the delivery mechanism (HTTP response codes, JSON converters, etc) but most of it should be delegated to other classes which also live in the controller layer. Model is not only about entities (state); it’s about all the behaviour of your system - the entire Domain Model.
The choice of View technology impacts on how your layers are coupled and how your application is deployed.
Keep the Delivery Mechanism decoupled from your Domain Model. The Domain Model should not know anything about how the business feature and data are delivered to the outside world.
Don’t couple all the layers of your application using frameworks that will tell our entities how they should be represented as JSON or persisted in a database. Use small libraries instead of big frameworks. Use a small library at your Controller layer to convert JSON to and from your domain objects and another small library inside your repositories to convert your entities to and from data in the database. If you are using Java, we created LightAccess for this purpose.
Keeping Delivery Mechanism decoupled from Domain Model give us options to deploy and scale the application.
Software es nuestra pasión.
Somos Software Craftspeople. Construimos software bien elaborado para nuestros clientes, ayudamos a los/as desarrolladores/as a mejorar en su oficio a través de la formación, la orientación y la tutoría. Ayudamos a las empresas a mejorar en la distribución de software.