Login with OpenID

Building a Smarter Model Layer

Written by Hector Virgen
Published on February 1, 2010
Last updated on February 4, 2010

I have been spending the last several months learning the "Data Mapper" pattern as suggested in the Zend Framework Quickstart. At first glance it seems simple, intuitive, and straight-forward -- the model contains the business rules, and the mapper is used to persist the model in the database. This separation of concerns can make the task of refactoring later much simpler and also allows for easier unit testing.

But when tasked at building a site that is more complex than a simple guestbook, the path is less clear.

My first attempt at implementing this pattern in my model layer at first proved to be useful. I started off by creating a series of "models", or objects that could be represented with nouns such as "user", "blog post", and "comment". I built an abstract model class that provided magic getters/setters. I created mappers for each model to persist them in a database. I created lazy-loading collections and reference objects so that I could traverse through related models as needed. And, finally, I introduced a service layer to create a single point of entry to my models.

// Abstract model class (simplified)
abstract class Virgen_Model_Abstract
{
    protected $_data = array();
    
    public function __get($key)
    {
        return $this->_data[$key];
    }
    
    public function __set($key, $value)
    {
        $this->_data[$key] = $value;
    }
}

class Default_Model_BlogPost extends Virgen_Model_Abstract
{
    protected $_data = array(
        'id' => null,
        'user' => null,
        /* ... */
    );
}

All was well -- or so it seemed.

When all was said and done, I noticed that my models were relatively thin. Most of them ended up as "dumb" containers of data and, thanks to my magic getters and setters, were more like glorified associative arrays. My service layer, however, was weighing in at a few thousand lines of code. So what happened?

It turns out that my models were suffering from anemia.

After reading Fowler's blog about anemic domain models, I decided to take a step back and start over. I wanted to be sure that if I'm going to use the data mapper pattern that I'm going to use it correctly.

With this in mind I put together two objectives:

  1. Remove the abstract model class. While useful, all that magic was making it too easy to be lazy.
  2. Clean up the service layer by moving all model-specific code into the model itself.

For the first part, I removed the abstract model class and redesigned my models to use real getters/setters for each property. This allowed me to enforce certain things like the user of a blog must be an instance of Default_Model_User.

// New blog post model (excerpt)
class Default_Model_BlogPost
{
    protected $_user;
    
    /* ... */
    
    public function setUser(Default_Model_User $user)
    {
        $this->_user = $user;
    }
    
    /* ... */
}

Now that my models have true getters/setters, I can be sure to include type hinting and even provide some functionality to them.

One of the areas that I've improved upon is the editing of the models through the use of forms. In my previous code, I used a service layer to create and populate a form:

$blogsService = new Default_Service_Blogs();
$blog = $blogsService->find(123);
$form = $blogsService->getEditForm($blog);

I've now moved that functionality into the model itself, meaning that a model is able to create and populate a form for editing itself:

$blogsService = new Default_Service_Blogs();
$blog = $blogsService->find(123);
$form = $blog->getForm();

This makes a big difference in what my blog model can do. Since it has access to it's own form, I can now use it for validation and also create new blog posts just as easily:

$blog = new Default_Model_BlogPost();
$form = $blog->getForm(); // returns empty form

Some of the things I am still working on is:

  • How to handle ACL checks. I previously had my service layer handle this, but with a thinning service layer I may need to support this directly in the model.
  • How to handle lazy-loading references for "belongs-to" or "has-one" relationships.

Overall, this new design appears to be overall more solid than my previous one. I am currently using it in a newly created VirgenTech website. I'm building it form the ground up using this design to see how well it works in a not-so-simple website.

Comments

blog comments powered by Disqus