The modern web has largely settled on Model-View-Controller (MVC) as the paradigm of choice. Though it may ultimately be described by different names, these components are the core of what makes most object-oriented web applications work. And most people know that the model is the most important element of the application. But crafting good models can be extremely challenging. Many developers end up putting code all over the place, with the logic that belongs in the model scattered throughout the view and the controller layers.
What ultimately makes a good model? Where does each aspect of the model belong? And what is to be made of the other layers of the application? Let’s explore together.
Separation of Concerns
Ultimately, the goal with MVC is to separate the concerns between the various layers. Presentation logic is confined to the view; we focus solely on the display, presentation and end-user manipulation of data here. If it involves HTML and templating, it belongs in the view.
The controller is a bit trickier. We’ve all heard the phrase, “fat model, skinny controller” before. What does this actually mean? The controller has a single job too: to mediate between the inputs from the request and the outputs for the presentation/view layer.
This is easier said than done. Controllers often get tasked with lots of other things, too: writing messages to the session, determining which view to display, catching and handling exceptions gracefully, and more. The problem here is, all of these responsibilities belong elsewhere in our application: they belong in the model.
The model contains all of our business logic and rules. Specifically the model encompasses our domain and our service layer together.
What is our service layer?
The service layer is a layer of components that communicate with lower level infrastructure pieces in our application. For example, if you’re sending email or processing a new user, this is likely going to be expressed as a service.
The service layer typically stands between the controller and the domain. When you call into the model, you typically interface with a service, handing it the inputs from the user’s request, and then transitioning the response to the presentation layer for display to the requestor.
Persistence belongs here, because persistence is a lower-level component of the larger application.
What is our domain?
The words “model” and “domain” are often used interchangeably, but in this context, they really mean very different things. The domain is a part of the model, but does not represent the whole of the model. In fact, the domain contains the business logic and rules that your application lives by. For example, if you’re setting the rules for how a new user is to be created, the validation for that would happen here, in these logic and business rules. Exceptions would be thrown for users that don’t meet the criteria, to be handled by the service layer.
Looking at the full stack
So let’s take a look at the full stack.
Here you can see that services and integrations belong solely in the services layer, but that the domain contains business logic and rules. This is an important element in separation of concerns. By separating out our concerns in this way, we achieve two things:
- Our domain is pure logic, data and rules-based, without concern for persistence (the holy grail in model writing); and
- Our service layer protects our domain from corruption by the view layer.
Each of these is important.
Persistence and the domain
Many models contain persistence and domain logic together in a single class (think Active Record). While these patters are useful for getting up and running fast, and can also offer powerful opportunities for larger web applications, from the purists’ perspective, these patterns fall short because they essentially combine two responsibilities into the same God object.
Here, instead, we’ve broken up persistence into the service layer, leaving the domain layer free to contain simple business rules and validation logic. In fact, the goal here is that our domain will focus solely on these components.
What are business rules and logic, anyway?
Business rules and business logic contain the essential elements that make your application unique.For example, if you are writing a time-tracking app and you want to enforce that no person can bill for less than 1/4th of an hour (15 minute increments), verifying and enforcing that constraint would belong here, in the domain layer. Violations would result in an exception, which would be handled by the service layer.
What’s the service layer’s job in all this? When someone logs time, you call the SaveTimeEntry service, which invokes the domain. The domain validates and accepts or rejects the time entry, and the service layer then persists it, or tells the client they made a mistake.
Coming soon: a new book on modern object-oriented PHP!
Love this article? Then you’re going to love my latest book, called Modern Object-Oriented PHP! It’s focused on giving you the tools you need to create well-crafted modern PHP applications. In it you’ll learn about interfaces, using final, domain driven design, event and command buses, and much more! Enter your email address below to get the sample chapter as soon as its ready, as well as special launch-day discounts!
Frustrated with your company’s development practices?
You don't have to be!
No matter what the issues are, they can be fixed. You can begin to shed light on these issues with my handy checklist.
Plus, I'll help you with strategies to approach the issues at the organization level and "punch above your weight."