Followup on rethinking Zend Models with DDD - Framework code included

July 16, 2009

As a follow up to my previous post I'm going to post some live examples of model layer with Zend framework based on Domain Driven Design concepts. I've spent the past few days studying up on domain driven design as well as a lot of Martin Fowler's work in relation to domain modeling and I think I have at least some code to start with that could get some conversations rolling. The issue is Zend Framework doesn't have a formal model layer and it shouldn't. Models are very much domain specific to what you're working on. As I've mentioned in my previous post sometimes you need complex business logic that will need to rapidly change and adapt to growing business needs. Active record can only get you so far when complexity expands.

I've posted a working Zend Framework project that has a basic working set of models so far. The current concept has a User who in turn owns a profile object. That profile object holds a nickname attribute that we can play around with. I have two tables users and profiles that are joined with a foreign key (user_id). Normally when you want profile information you'd just do a join and work with the result. What if that profile becomes context sensitive? What if a user can have a different profile based on the location he's in? Maybe he's a CIA agent and if he's overseas he needs a different profile to be covert. You'd now have to start adding conditions to your logic to account for this. Having the user own a profile object that can be injected at runtime allows for these contexts to be more manageable.

What if your user is going to be used in a workflow engine, a piece of marketing business logic that operates on users, it will be nice to be able to pass around a complete User object that has well documented properties and APIs instead of forcing each business logic service to know the details of your database.

Here is the google code repository you can checkout and play around with: Google Code Repository for Zend Model project
It's all working, just create a database and dump in the zendmodel_DB.sql file, change your config.ini file settings and you're all set assuming you handle your apache settings

Take a look at the default/controllers/IndexController.php file for how the client code will be used.

  2. // instantiate the user repository who will be in charge of building a proper user
  3. $userRepo = new user_UserRepository();
  4. // find a user with id 1 in the DB and grab their current nickname from the profile
  5. $user = $userRepo->find(1);
  6. $this->view->nickname = $user->Profile->getNickname();
  7. // set their nickname in their profile to something new
  8. $user->Profile->setNickname('tommy tom');
  9. // find the user again, it should pull the same object reference and not create a brand new object (Identity Map)
  10. $user = $userRepo->find(1);
  11. $this->view->nickname2 = $user->Profile->getNickname();
  12. // a fictional web service to check to see how popular your new nickname is
  13. $profileService = new services_ProfileService();
  14. $popularity = $profileService->howCommonIsMyNickname($user);
  15. $this->view->popularity = $popularity;
  16. // let's update the user's email address
  17. $user->setEmailAddress('');
  18. // you can manually commit the save to the database like this or in the destruct method of ObjectManager
  19. // it will do it automatically for you, this will save all the changes we've made to all our objects
  20. // using the unit of work pattern in ObjectManager, the profile and the user will get updated
  21. ObjectManager::performOperations();

We have a User model and we have some supporting files around it.
in models/user/ you'll see some files:

Each one plays a role within the domain User.php is the model itself that models a user in our system. It contains no SQL or knowledge of the persistence layer and can be passed around to any app on ths system. UserMapper.php is our basic DB Mapper that is in charge of doing the dirty work of putting things into our database. UserRepository is the default User Factory for creating a valid User object along with it's dependencies. Because this could be context sensitive we can contain that logic in a central place in there or add another Repository.

if you look in models/infrastructure/ObjectManager.php you'll see this file is a combination identity map and unit of work. It's job is to make sure if we have already instantiated a User object for example that we get back the same exact one and don't try to recreate it. It's basically an object cache.

Anyway, take a look and let's talk about what's right and what's wrong and how we can improve the complex domain model with Zend Framework. I'd like to start building on this example with everyday issues like logging, authentication, transactions, validation, unit tests, etc...


RSS feed for comments on this post.

  1. Orhan SAGLAM says:
    July 17, 2009 @ 16:10 — Reply

    Great article and sample project, I have been studying the DDD recently too..

  2. Giorgio Sironi says:
    July 18, 2009 @ 03:19 — Reply

    I think the testability point of view can be improved. A Repository often has an interface to decouple its implementation from the application level code, like the one in the controllers. By the way this is a particular implementation of the UserRepository which persists objects in the database: so it should ask for a connection (or database adapter or data mapper) in the constructor to be testable. Later you can create unit test where a sqlite in-memory connection is used. The ObjectManager class it is an expression of the UnitOfWork pattern, and should be a concrete object (not static) for the same testing and decoupling purpose. Remember that code that is easy to test is decoupled and maintainable code.

  3. Orhan SAGLAM says:
    July 18, 2009 @ 03:40 — Reply

    1- Let's say in some point you have decided to use doctrine orm instead of zend_db, would it be sufficent just to add a new repository and mapper class (user_doctrine_userRepository and user_doctrine_userMapper) ? 2- To be able to swtich between different datasources like webservice or Myqsl , instead of hardcoding the repository creation in the controller, would it be logic to create a manager class ? Sorry for my English if my questions are not understandable. Thank you.

  4. Michael Gatto says:
    July 18, 2009 @ 11:34 — Reply

    Jim, some of the code in this posting can be done today with Doctrine. //find user with id = 22 $user = Doctrine::getTable('Users')->find(22); $this->view->nickname = $user->Profile->nickname; //linking with Profile object is automatic when foreign key is specified in schema. //...your profile service $user->nickname = "Mikey"; $user->save(); This part of your architecture may well be covered by current Doctrine functionality. My domain objects encapsulate business rules but proxy transparently to doctrine's methods with __call(), __get() and __set() when they need data pulled from the db. I treat all of Doctrine's autogenerated classes as my Data layer. YMMV.

  5. Jim Plush says:
    July 19, 2009 @ 06:49 — Reply

    hi Michael, I took a look at doctrine previously and even according to their own docs they only support an active record type pattern now. "Doctrine is in a continuous development process and we always try to add new features that provide more freedom in the modeling of the domain. However, as long as Doctrine remains mainly an ActiveRecord approach, there will always be a pretty large, (forced) similarity of these two models." The issue I have which they say doctrine doesn't yet support is I have a large existing database I have to map over that has a model spread of 10's of tables possibly. Doctrine apparently wants a closer relation between the db tables and the model. Unless they've changed something recently?

  6. Ryan Foster says:
    July 20, 2009 @ 10:47 — Reply

    Very informative article, but how are you handling data validation? It seems that it would be the responsibility of the *Mapper classes, but if you're relying on the ObjectMapper to save all objects, how to you alert a user if data validation fails? Data validation exceptions?

  7. Jim Plush says:
    July 20, 2009 @ 12:12 — Reply

    hi Ryan, I'm actually working on the validation example now Validation in the mapper classes would only be concerned with validations on the data level such as is this string larger than my table's varchar 10 field? A higher abstraction will be needed because validation could be very context sensitive. If you have a Product model it may be valid to put it into a shopping cart, but it may not be valid to put it into an inventory request operation.

  8. Ryan Foster says:
    July 20, 2009 @ 16:30 — Reply

    I'll be anxiously awaiting the validation example. One more thought - if the ObjectManager is responsible for tracking (and saving) 'dirty' objects, wouldn't there be an extra save query issued if the repository's save method is called manually (before the ObjectManager is destroyed)? Or should the repository notify the object to mark itself 'clean' after a successful save?

  9. Orhan SAGLAM says:
    July 21, 2009 @ 03:54 — Reply

    Hi Jim, is there a collection class in your DDD implentation? $userRepo = new user_UserRepository(); $users = $userRepo->findAll(); if there was a method findAll(). What type of class should it return a userCollection class?

  10. Jim Plush says:
    July 21, 2009 @ 13:40 — Reply

    hi Ryan, yes I'm changing the implmentation of a manual save to call a make clean on the object to remove it from the dirty array Orhan, the findAll will indeed return a Users collection that will be implmented in the near future. I'm going to lazy load it so you don't get back a whole collection of instantiated users for performance reasons.

  11. Duo Zheng says:
    July 23, 2009 @ 10:41 — Reply

    Hey just wanted to say it's nice to see more attempts at DDD in php. I'll be sure to follow and provide feedback.

  12. Louis Vuitton Handbags Replica says:
    January 29, 2010 @ 20:38 — Reply

    Comment pending moderation

  13. Buy Pandora Online UK says:
    March 22, 2010 @ 01:01 — Reply

    Comment pending moderation

  14. blu ray ripper says:
    April 18, 2010 @ 04:51 — Reply

    Comment pending moderation

  15. Satellite High Speed Internet says:
    May 6, 2010 @ 19:38 — Reply

    Comment pending moderation

  16. Manolo blahnik says:
    May 14, 2010 @ 05:11 — Reply

    Comment pending moderation

  17. Manolo blahnik says:
    May 14, 2010 @ 05:14 — Reply

    Comment pending moderation

  18. foreign exchange rates says:
    June 5, 2010 @ 00:16 — Reply

    Comment pending moderation

  19. Ed Hardy Clothing says:
    June 7, 2010 @ 18:35 — Reply

    Comment pending moderation

  20. louis vuitton says:
    June 9, 2010 @ 22:12 — Reply

    Comment pending moderation

  21. piano tutorial says:
    June 15, 2010 @ 06:11 — Reply

    Comment pending moderation

  22. Indonesia Furniture Handicraft Wholesale Marketplace says:
    June 15, 2010 @ 10:29 — Reply

    Comment pending moderation

  23. Louis Vuitton handbags says:
    June 16, 2010 @ 00:18 — Reply

    Comment pending moderation

  24. Louis Vuitton handbags says:
    June 16, 2010 @ 00:29 — Reply

    Comment pending moderation

  25. evening dresses says:
    June 18, 2010 @ 08:30 — Reply

    Comment pending moderation

  26. Microsoft Office 2007 says:
    June 19, 2010 @ 02:32 — Reply

    Comment pending moderation

  27. Mike says:
    June 21, 2010 @ 23:54 — Reply

    Comment pending moderation

  28. discount nfl jerseys says:
    June 22, 2010 @ 02:53 — Reply

    Comment pending moderation

  29. jewellery earrings says:
    June 24, 2010 @ 01:34 — Reply

    Comment pending moderation

  30. replica watches says:
    June 25, 2010 @ 00:57 — Reply

    Comment pending moderation

Leave a Comment

Line and paragraph breaks automatic, HTML allowed: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <code> <em> <i> <strike> <strong>

Comments disabled due to spammers being losers that lead sad lives.