Solution: The Model Has an Active Record
Let's solve the antipattern Magic Beans using Active Record.
Controllers handle application input while views handle application output, both relatively simple and well-defined tasks. Frameworks are best at helping us put these together quickly. But it’s hard for a framework to provide a one-size-fits-all solution for models because models comprise the rest of the object-oriented design for our application.
This is where we actually need to identify what the objects are in our application and what data and behavior those objects have. It’s true what Robert L. Glass said:the majority of software development is intellectual and creative.
Grasping the model
Fortunately, there’s a lot of wisdom in the field of object-oriented design to guide us. Craig Larman’s book
Information expert
The object responsible for an operation should have all the data needed to fulfill that operation. Since some operations in our application involve multiple tables (or no tables) and Active Record is good at working with only one table at a time, we need another class to aggregate several database access objects together and use them for the composite operation.
The relationship between a model and a Data Access Object like Active Record should be HAS-A (aggregation) instead of IS-A (inheritance). Most frameworks that rely on Active Record assume the IS-A solution. If our model uses Data Access Objects instead of inheriting from the Data Access Object class, then we can design the model to contain all the data and code for the domain that it’s supposed to model — even if it takes multiple database tables to represent it.
Creator
How the model persists its data in a database should be an internal implementation detail. A domain model that aggregates its Data Access Objects should have the responsibility to create those objects.
The controllers and views in our application should use the domain model interface without being aware of what kind of database interaction is necessary for the model to fetch or store data. This makes it easy to change the database queries later in one place in our application.
Low coupling
It’s important to decouple logically independent blocks of code. This gives us the flexibility to change the implementation of a class without affecting its consumers. We can’t simplify the requirements of the application; some complexity has to reside somewhere in our code. But we can make the best choice about where we implement that complexity.
High cohesion
The interface for the domain model class should reflect its intended usage, not the physical database structure or CRUD operations. Generic methods of the Active Record interface like find()
, first()
, insert()
, and even save()
don’t tell us much about how they apply to application requirements. Methods like assignUser()
are more descriptive, and our controller code is much easier to understand.
When we decouple a model class from the Data Access Object it uses, we can even design more than one model class for the same Data Access Object. This is better for cohesion than trying to combine all work related to the given tables into a single class and extending Active Record.
Putting the domain model into action
In Domain-Driven Design:
A model in the original MVC sense — not in the fallacious software sense — is an object-oriented representation of a domain in our application, that is, the business rules in our application and the data for those business rules. The model is where we implement business logic for the application; storing it in a database is an internal implementation detail of a model.
Once we have the model designed around concepts in our application, we can start implementing database operations that are completely hidden within our model classes instead of database layout. Let’s look at a possible refactoring of our earlier example code:
Get hands-on with 1200+ tech skills courses.