Federico Cargnelutti

Simple is better than complex. Complex is better than complicated. | @fedecarg

Domain-Driven Design with Zend Framework

with 26 comments

Part 1: Domain-Driven Design and MVC Architectures
Part 2: Domain-Driven Design: Data Access Strategies
Part 3: Domain-Driven Design: The Repository

Some of the Domain-driven design concepts explained in my previous posts are applied in this sample application. I’m also going to use the Zend Framework infrastructure to speed up some development tasks.

Directory Structure

app/     -> Application and UI Layers
domain/  -> Domain Layer
lib/     -> Infrastructure Layer

Application and UI Layers

app/
    controllers/
        UserController.php
    views/
        layouts/
        scripts/

Domain Layer

This layer should be well separated from the other layers and it should have few dependencies on the framework(s) you are using. I’m going to group all the classes and interfaces under the namespace “Project” and add it to my include path:

domain/
    Project/
        Dao/
            Db/
                IUser.php
                IUserProfile.php
                User.php
                UserProfile.php
        Model/
            User/
                IRepository.php
                Repository.php
            User.php
            UserProfile.php
            Users.php

Infrastructure Layer

This layer acts as a supporting library for all the other layers:

lib/
    Zend/
        ...
    Zf/
        Db/
            Adapter.php
            Exception.php
            ReplicationAdapter.php
        Domain/
            Collection.php
            Entity.php
            Exception.php
            Repository.php

User Entity

The User and UserProfile objects have a one-to-one relationship and form an Aggregate. An Aggregate is as a collection of related objects that have references between each other. Within an Aggregate there’s always an Aggregate Root (parent Entity), in this case User:

class Project_Model_User extends Zf_Domain_Entity
{
    /* @var int */
    private $id;

    /* @var string */
    private $name;

    /* @var Project_Model_UserProfile */
    private $profile;

    public function __construct($properties)
    {
        $this->define($properties);
    }
}

Usage:

$array = array('id'=>1, 'name'=>'Federico');
$user = new Project_Model_User($array);
$user->setProfile($profile);

echo $user->getId();   // Outputs 1
echo $user->getName(); // Outputs Federico

Users Collection

A collection is simply an object that groups multiple elements into a single unit.

class Project_Model_Users extends Zf_Domain_Collection
{
    public function __construct($users)
    {
        foreach ($users as $user) {
            if (!($user instanceof Project_Model_User)) {
                throw new Zf_Domain_Exception(...);
            }
            $this->append($user);
        }
    }
}

User DAO

The UserDAO class allows data access mechanisms to change independently of the code that uses the data:

class Project_Dao_Db_User extends Zf_Db_Adapter
{
    public function find($id)
    {
        $db = $this->getConnection();
        $query = $db->select();
        $query->from('user');
        $query->where('id = ?', $id);
        $result = $db->fetchRow($query);

        return $result;
    }

    public function findAll()
    {
        ...
        return $resultSet;
    }
}

User Repository

A Repository is basically a collection of Aggregate Roots. Collections are used to store, retrieve and manipulate Entities and Value objects, however, object management is beyond the scope of this post.

The UserRepository object injects dependencies on demand, making the instantiation process inexpensive. A caller method is responsible for dynamically creating the setter and getter methods. You can easily mock objects by passing a custom config array via the constructor.

class Project_Model_User_Repository extends Zf_Domain_Repository
{
    /* @var Project_Dao_Db_User */
    private $userDao;

    /* @var Project_Dao_Db_UserProfile */
    private $userProfileDao;

    /* @var array IoC Spec */
    private $inject = array(
        'userDao'        => 'Project_Dao_Db_User',
        'userProfileDao' => 'Project_Dao_Db_UserProfile'
    );

    public function getUserById($id)
    {
        $row = $this->getUserDao()->find($id);
        $user = new Project_Model_User($row);

        $row = $this->getUserProfileDao()->findByUserId($id);
        $profile = new Project_Model_Profile($row);
        $user->setProfile($profile);

        return $user;
    }

    public function getUsers()
    {
        $users = array();
        $rows = $this->getUserDao()->findAll();
        foreach ($rows as $row) {
            $users[] = new Project_Model_User($row);
        }

        return new Project_Model_Users($users);
    }
}

Usage:

$repo = new Project_Model_User_Repository();
$user = $repo->getUserById(1);
$profile = $user->getProfile();
$users = $repo->getUsers();

Source Code

http://fedecarg.com/projects/show/zfdomain
http://fedecarg.com/projects/show/zfreplicationadapter

Links

If you’re interested in learning more about Domain-driven design, I recommend the following articles:

Domain Driven Design and Development In Practice
Domain-Driven Design in an Evolving Architecture

Written by Federico

March 22, 2009 at 2:06 pm

26 Responses

Subscribe to comments with RSS.

  1. I think this is more or less what Doctrine 2 will be, I hope.

    Giorgio Sironi

    March 22, 2009 at 4:30 pm

  2. Is there any reason why the “entities” need extend a class? The whole movement in Java was to turn away from entity beans to using POJOs. This also makes your business objects more portable. They should have *no* dependency on anything. The responsibility of “hydrating” those objects and persisting them should be the responsibility of a third part transparent object. This is the way its done in EJB3. Unfortunately, in the PHP world, nothing like this exists. The closest things are the Outlet and Repose projects, which don’t seem to be moving very quickly, and Doctrine 2.0 which doesn’t exists yet.

    Avi

    March 22, 2009 at 6:41 pm

  3. @Avi Yes, I agree with you, but PHP doesn’t support annotations, such as Configurable, Entity and OneToOne. I’m simulating marker annotations using OO techniques. You could, however, use a DI container, add EntityManager and EntityManagerFactory, and create your own persistence package. I believe Symfony 2 is going that direction. The combination of Doctrine and DDD makes DAO’s redundant.

    Federico

    March 22, 2009 at 7:52 pm

  4. @Avi: Well, Doctrine 2 is currently in development:
    http://svn.doctrine-project.org/trunk/
    and it requires php 5.3… I think we’ll view it together Zend Framework 2.0…

    Giorgio Sironi

    March 22, 2009 at 9:47 pm

  5. Great article, I am implementing something similar with zend framework and doctrine..

    Orhan SAGLAM

    March 25, 2009 at 12:14 am

  6. In your final example of the User Repository, getUserDao() and getUserProfileDao() are handled by the __call() function.

    But where do you inject the actual DB adapter into the UserDao and UserProfileDao?

    Matt

    April 1, 2009 at 7:17 pm

  7. I’m extending Zf_Persistence_Db_Adapter and getting the adapter from the registry. However, using Zend_Registry is not the best solution, as it introduces a hidden dependency.

    Federico

    April 1, 2009 at 10:48 pm

  8. Thanks for the tutorial series.

    One thing that confuses me in this example is the purpose of abstracting the data layer.
    I.e. Why is it good to do it?

    Lets say , for example, that you suddenly decide to add a new property to Project_Model_User. You are going to have to also add a new column to the db table.

    Or, say, you come to realise that you have three types of user and hence need to make the user ID referenced by two fields UserTypeId+UserId to ensure they are unique within the app.

    Or, say you suddenly decide to use xml to persist the data – the model and app still have to change to accommodate.

    If a change in the db leads to a change in the model classes and a change in the model classes leads to a change in the db/persistance layer, why are we so desperate to keep them so separated?

    ronny stalker

    April 3, 2009 at 9:14 am

  9. Federico,
    Thanks for the explanation great series btw. I am learning a lot.

    Another question

    You give the example of:

    $repo = new Project_Model_User_Repository();
    $user = $repo->getUserById(1);
    $profile = $user->getProfile();
    $users = $repo->getUsers();

    for retrieving a single user and a collection of users.

    But could you show an example of how you would use your current framework to to create a new user or delete a user?

    Would you instantiate a new user entity and populate it and pass it to createUser() on the UserRepository, or would you pass a array of new user data to createUser() on the UserRepository?

    Matt

    April 3, 2009 at 3:23 pm

  10. Hi Ronny,

    When you abstract you do it to reduce details so that other developers can focus on a few concepts at a time. Keep in mind that software architectures are constantly evolving and changing. I guess abstraction helps deal with those changes in a more efficient way.

    Federico

    April 3, 2009 at 7:39 pm

  11. Hi Matt,

    I would have an addUser() method in the Repository and a save() or saveFromArray() method in the DAO:

    $array = array(‘name’=>’Jim’);
    $user = new Project_Model_User($array);

    $repo = new Project_Model_User_Repository();
    $repo->addUser($user);

    And use the Repository to pass the data to the DAO:

    $this->getUserDao()->save($user);
    $this->getUserDao()->saveFromArray($array);

    Federico

    April 3, 2009 at 7:40 pm

  12. > If a change in the db leads to a change in the model classes and a change in the model classes leads to a change in the db/persistance layer, why are we so desperate to keep them so separated?

    Hi Ronny, I’ve just realized that your question remained unanswered :)

    If you remove the properties field from the Project_Model_User class, then you are not defining any class properties, and you are free to pass n amount of array elements via the constructor.

    Federico

    April 7, 2009 at 12:59 pm

  13. > Hi Matt,

    I would have an addUser() method in the Repository and a save() or saveFromArray() method in the DAO:

    …..

    Hey Federico,

    With this method of adding and saving users. Where do propose to handle input validation? (ie. “This username already exists” or “Invalid email adddress”).

    Within the Project_Model_User or Project_Model_User_Repository?

    Matt

    April 8, 2009 at 4:49 pm

  14. Any chance of you writing a beginner level tutorials about domain driven design that doesn’t require the zend framework? Not suggesting any particular framework but I’ve read all your articles and many others and I still can’t fully grasp it all (and zend framework doesn’t work on my ancient servers). I’d like to see something that goes into the core concepts using generic and easily understandable code that even a novice can then take and re-implement in their framework of choice.

    menoknow

    April 11, 2009 at 2:19 am

  15. [...] Domain-Driven Design with Zend Framework [...]

  16. Thanks for your answer Federico.

    Since writing those previous questions I have read the Domain Driven Design Book (Eric Evans). Now, when I come back to this tutorial series I’m reading it in a whole new light and these explanations are even more useful as i try to carve out my fist DDD bits of PHP code. So, thanks for pointing me in the direction of DDD- its a revelation.

    Amongst that, gushing, compliment is the answer menoknow’s question – read that DDD book for good intro – its worth it.

    ronny stalker

    May 15, 2009 at 5:11 am

  17. Hi Federico,

    In your example you have a profile Object stored in the User Aggregate. As I understand it, you are currently always loading the profile data in the user object when you retreive a user object. Suppose this User Aggregate would extend and thus, would have extra entity’s/value objects related to it. In this example you would always load all the data for the User Aggregate but this seems not the best solution because you will create overhead (i.e. extra query’s to a database for example) where the data you are querying is not necesarry.
    It seems more logical to just load the “basic” data in the user aggregate and only load the extra data when it’s necesarry (i.e. only load the profile data when you actually call it $user->getProfile()). However when I follow this approach I would add something like this to the getProfile method:
    if(null == ($this->_profile)) {
    //retreive data and store it in profile field
    }
    return $this_profile;
    This implementation however is not valid because you would couple a db class or a repository to the User class.
    How would you solve this issue, or am I mistaking on this one?

    Bert

    May 16, 2009 at 3:38 pm

  18. Hi Federico,
    First of all, your tutorials provide great insight into Domain Driven Design and Thanks.

    I am also interesting in finding out how to implement what Bert has mentioned above.

    Andho

    May 18, 2009 at 8:34 am

  19. Yes Bert, that’s right. Zf_Domain_Entity is responsible for returning a single instance of the profile object.

    Line 130:
    http://svn.fedecarg.com/repo/Zf/Domain/Entity.php

    Federico

    May 18, 2009 at 10:57 am

  20. Federico,

    I think we are talking about two different things. The profile is indeed an entitiy but that was not really my question.
    Suppose the Person Aggregate would have a Profile entity, an Address Entity, … and some other stuff related to the Person Aggregate. How would you manage the loading of those objects.
    Would you load all the entities of the Aggregate at once (so in fact when you retreive the aggregate from the repository, you load all the data). This implementation seems to have lots of overhead when you only need one field from the root Aggregate for example.
    Another implementation could be that you only initiate the main properties of the root Aggregate (=Person) and that you would only load the entities like Profile or Address when you need them (so in fact you would lazy load these entities with a proxy pattern for example).
    What is your opinion on this

    Bert

    May 18, 2009 at 12:13 pm

  21. Gotcha. I guess that, if you are working with DAOs, there are different ways of doing it. You already mentioned 2 of them I think. You could also have a repository that represents the relationship between both entities, for example, UserProfileRepository. And then use UserProfileDAO to query the database, join the User and Profile tables and return the data to the repository so it can populate the User and Profile objects.

    I don’t know if this is allowed in DDD, but I haven’t develop a Repository that manages this complexity for me yet.

    Federico

    May 18, 2009 at 4:37 pm

  22. Federico,

    As far as I know, only Aggregate Root is recomended to have a Repository. I’m really stuck with such implementation details. DDD concepts are obviosly interesting, but I’ve lots of misunderstandings relative to implementation :\

    Roman

    October 15, 2009 at 7:24 am

  23. [...] Read this article: Domain-Driven Design with Zend Framework « Federico Cargnelutti [...]

  24. [...] Por otra parte los mappers heredarán de Zend_Mapper. A esta nueva orientación se le denomina Domain Driven Design (DDD). Estoy siguiendo el tema con este libro y la verdad es que todo tiene muy buena pinta, aunque [...]

  25. Hi,

    I’m confused about domain and service, can someone provide a sample code for domain and service in action?

    Thanks

    Juan

    February 6, 2010 at 12:41 am

  26. Repositories are not part of the Service Layer, they are part of the Domain Layer. The Repository provides a higher level of data manipulation, and acts as a link between the Domain and Persistence Layers (Entity – Repository – DAO).

    Service classes are not part of the Domain or Persistence Layers and therefore do not provide methods to access data, such as finders. Finders are usually found in the Repository.

    Federico

    February 8, 2010 at 10:20 am


Leave a Reply