Page 1 of 2

Building complex domain models

Posted: Sat Jan 03, 2009 3:17 am
by matthijs
So I have my Front Controller in place, which routes nicely to Controllers, I have some Views which render a couple of Templates, etc. But one main issue I'm having difficulty with is how to build up my models.

Recently some people have given Models some more attention, for example see these excellent posts
http://blog.astrumfutura.com/archives/3 ... iated.html
http://weierophinney.net/matthew/archiv ... cture.html

The way I want to build my models is making them as fat as needed, to ensure all responsibilities of the models are in fact in the model. For example, I want the validation rules and filters to be in my model classes, not spread out over several controller files using the model classes.

So you can do

Code: Select all

 
class User {
    /* */
}
// set a value and save
$user = new User();
$user->set('name','mike');
// ask the model if the data is valid
if($user->isValid()){
  $user->save();
}
 
But the problem is in the implementation with more complex models. For a simple model like a User, which maps nicely to a single database table, you would have a class like:

Code: Select all

 
class User {
  protected $fields = array();
  protected $rules = array();
  protected $filters = arrayt();
  public function __construct(){
    // initiate fields and rules and filters for each field here
  }
  public function set ($field, $value) {}
  public function get ($field) {}
  public function save(){
    // insert code to save the field data to for example a user table in db
  }
}
 
However, in most cases models are much more complex then that. One model object would have relationships with other model objects. For example, in a content management system, an Article object would "contain" data about the article itself (title, body, excerpt), but also data about the author (belongs to Author object), data about categories (Category objects), tags, etc. But of course, in practice you don't want your Article class to duplicate all the business logic belonging to the Author, Category and Tag classes/models.

How do you handle these kinds of more complex business models? I have read many tutorials and books but all examples so far deal with simple one-on-one relationships between classes and tables, which is relatively simple (even though the examples in Mathews blog posts are complex enough). And I haven't even started about using (nested) transactions or foreign key restrictions in the models (assuming the use of a database, but in theory the model will be independent from the storage).

What I'm looking for is some general thoughts about this, maybe a few snippets of code or links to more complex examples.

Re: Building complex domain models

Posted: Sat Jan 03, 2009 3:48 am
by Chris Corbyn
If I understand correctly your main question is how to deal with relationships in domain objects?

Pseudo:

Code: Select all

$article->setAuthor($author);
 
...
 
function setAuthor($author) {
  $this->_author = $author;
}
 
function getAuthor() {
  if (!isset($this->_author)) {
    $this->_author = finderFor('author')
      ->findOneByAuthorId($this->getValue('authorid'));
  }
  return $this->_author;
}
If you wanted to you could override the isValid() check so that it checks any loaded author objects and calls their isValid() methods too. Or you could check $author->isValid() within setAuthor().

Re: Building complex domain models

Posted: Sat Jan 03, 2009 5:07 am
by matthijs
Chris Corbyn wrote:If I understand correctly your main question is how to deal with relationships in domain objects?
Yes, I think so. Building one model class with a few fields and methods is doable, but when you have multiple model objects, with different kind of relationships between them (1:1, 1:n, n:n, different business rules about the relationships, etc) it gets complicated.

The code example you gave does give me some new ideas. Until now I hadn't thought about having separate setters for the "foreign" data fields of the model. With the foreign fields I mean the $author in an Article, and the $tags, $categories, etc. The opposite of the foreign fields would be the "native data fields like $articletitle, $articletext, etc
Chris Corbyn wrote:If you wanted to you could override the isValid() check so that it checks any loaded author objects and calls their isValid() methods too.
What exactly do you mean with this?

Re: Building complex domain models

Posted: Sat Jan 03, 2009 4:31 pm
by Eran
However, in most cases models are much more complex then that. One model object would have relationships with other model objects. For example, in a content management system, an Article object would "contain" data about the article itself (title, body, excerpt), but also data about the author (belongs to Author object), data about categories (Category objects), tags, etc. But of course, in practice you don't want your Article class to duplicate all the business logic belonging to the Author, Category and Tag classes/models.
In the way I usually go about it, the article object wouldn't contain data about other models such as authors or categories. It would just know what models to ask the information from when he needs it. Relationships are currently implemented using something similar to the reference map the ZF table gateway class has, only it's used for querying across tables in a generic manner (actually I think they implemented something similar in later versions).

This works well for now, but I think in the future I will implement a separate class hierarchy for defining relationships between models.

Re: Building complex domain models

Posted: Sat Jan 03, 2009 9:15 pm
by Chris Corbyn
matthijs wrote:The code example you gave does give me some new ideas. Until now I hadn't thought about having separate setters for the "foreign" data fields of the model. With the foreign fields I mean the $author in an Article, and the $tags, $categories, etc. The opposite of the foreign fields would be the "native data fields like $articletitle, $articletext, etc
I don't use "separate" setters for just the related domain objects, I use setters and getters for all domain object fields, so there's no inconsistency in my interface. Some fields take numbers, some take strings, some take domain objects. It depends on what the field represents.
matthijs wrote:
Chris Corbyn wrote:If you wanted to you could override the isValid() check so that it checks any loaded author objects and calls their isValid() methods too.
What exactly do you mean with this?
I just mean that $article->isValid() might not *only* check if the article fields are valid, it could also invoke the $author->isValid() method.

Code: Select all

function isValid() {
  if (isset($this->_author) && !$this->_author->isValid())
  {
    return false;
  }
  // ... normal validation logic here
  
}

Re: Building complex domain models

Posted: Sat Jan 03, 2009 9:48 pm
by allspiritseve
pytrin wrote:In the way I usually go about it, the article object wouldn't contain data about other models such as authors or categories. It would just know what models to ask the information from when he needs it. Relationships are currently implemented using something similar to the reference map the ZF table gateway class has, only it's used for querying across tables in a generic manner (actually I think they implemented something similar in later versions).
I don't know, I feel like that is unnecessary abstraction (keeping relationships outside of the domain objects). The way I look at it, a property is a property whether it is a single value, an array, or an object. In fact, I'd consider an object property to just be a refactoring of several related properties. A beginning Post object might have properties author_id, author_name, etc., and then later moved into a property author which is an Author object. That corresponds with the domain concepts: a Post wouldn't be a Post without an Author... so why abstract away that relationship?

Re: Building complex domain models

Posted: Sat Jan 03, 2009 10:54 pm
by Eran
The problem is that all models involved in a relationship might need to know the details of the relationship between them. Keeping the relationship in the model often means duplication of the relationship, and this is what I would like to avoid.

In your example, assume I want to retrieve all the posts for an author - the author model needs to know about the relationship as well as the posts model.

There is a tendency in more agile projects, as the requirements keep changing and more domain logic is added, that more models need to know about previously simple relationships. What I would like to create is a system in which models simply queries for a relationship by name, and get back all the details they need. This way if (when) the relationship changes, the changes will propagate naturally through the application.

Re: Building complex domain models

Posted: Sat Jan 03, 2009 11:00 pm
by allspiritseve
pytrin wrote:In your example, assume I want to retrieve all the posts for an author - the author model needs to know about the relationship as well as the posts model.
Well, I use datamappers, so that's not an issue for me... :D

For true two-way relationships, though, I don't see any reason why each cannot know about the other. What sort of duplication are you worried about? It's just an object reference.

Re: Building complex domain models

Posted: Sat Jan 03, 2009 11:10 pm
by Eran
I use datamappers
By datamappers, do you mean automapping ORM? (like propel?) I feel those approaches are not very well suited for the web, and generally prefer the Table DataGateway pattern.
For true two-way relationships, though, I don't see any reason why each cannot know about the other. What sort of duplication are you worried about? It's just an object reference.
In order to get the reference, the classes need to define the relationship. For example, the author class needs to know that a blog object 'parent_id' property maps to its own 'id' property. The post class needs to know the same relationship, only from the opposite direction. My objective is to really map this relationship into a one reference (named string?) which both classes can inquire to get the details of the relationship.

Re: Building complex domain models

Posted: Sat Jan 03, 2009 11:26 pm
by allspiritseve
pytrin wrote:By datamappers, do you mean automapping ORM? (like propel?) I feel those approaches are not very well suited for the web, and generally prefer the Table DataGateway pattern.
By datamappers I mean custom hand-coded mappers that have a Repository interface and map between objects and tables. They're pretty simple, arborint and I kind of decided they were a hybrid between the datamapper and TDG patterns.
pytrin wrote:In order to get the reference, the classes need to define the relationship. For example, the author class needs to know that a blog object 'parent_id' property maps to its own 'id' property. The post class needs to know the same relationship, only from the opposite direction. My objective is to really map this relationship into a one reference (named string?) which both classes can inquire to get the details of the relationship.
Not if you use setters like Chris Corbyn mentioned. You could have something like this in a datamapper:
function constructPost ($array) {
$post->setSubject ($array['subject']);
$post->setBody ($array['body']);
$post->setAuthor ($this->mapperRegistry->get ('Author')->getById ($array['author_id']));
}
Thus, the knowledge of db foreign keys are kept out of the domain object and in the mapper where they belong. The domain object gets its reference, and everybody's happy. Well, maybe not those using ActiveRecord... ;)

Re: Building complex domain models

Posted: Sat Jan 03, 2009 11:30 pm
by Eran
I'm not sure if I understand completely what your mapper does (last time I looked at skeleton, mapper was doing way too much for my liking...) but this is exactly what I'm talking about, so where's the argument?

I want to implement a mapping system similar to what you describe, only it pertains only to database relationships. Didn't you say before that keeping the relationships outside of the models is an unnecessary abstraction?

Re: Building complex domain models

Posted: Sun Jan 04, 2009 12:17 am
by allspiritseve
pytrin wrote:I'm not sure if I understand completely what your mapper does (last time I looked at skeleton, mapper was doing way too much for my liking...)
It's not Skeleton code, it's my code, Skeleton's datamapper is very different. We are working on a new ORM project along with jshpro2 that is based on Fowler's ORM patterns though.
pytrin wrote:but this is exactly what I'm talking about, so where's the argument? I want to implement a mapping system similar to what you describe, only it pertains only to database relationships. Didn't you say before that keeping the relationships outside of the models is an unnecessary abstraction?
Maybe I misunderstood what you were talking about, but it sounded like your domain objects were holding a reference to something that would return the correct related object if passed the author_id, like so:

Code: Select all

function getAuthor()    {
    return $this->referenceMap->get ('author')->getById ($this->author_id);
}
Whereas with mine:

Code: Select all

function getAuthor()    {
    return $this->author;
}
I could be misunderstanding... it actually sounded even more abstract than that, I'm just guessing at the implementation.

Re: Building complex domain models

Posted: Sun Jan 04, 2009 12:50 am
by Eran
What you're describing in this example is already the result object. I'm talking about Table gateway methods that compose the queries to populate the data in those row objects.

Code: Select all

<?php
class Posts extends Table_Datagateway {
    protected $_referenceMap = array(
        'Author' => array(
            'columns' => 'author_id',
            'refTableClass' => 'Authors',
            'refColumns' => 'id'
        )
    );
 
    public function getAuthors() {
        $rows = $this -> get('*')
              -> by('Author')
              -> query();
        return new PostsSet($rows);
    }
}
The by() method is where the reference takes place. Currently I'm using Zend's Db_Table class which has an array reference map for mapping table relationships. I use this map to also compose my join queries by using named keys in the map.

What I want to do next (when I have the time and energy) is to create a mapper class that this by() method can query instead of the reference map, and would hold relationships outside of the model so many models can reference the same relationship.

Re: Building complex domain models

Posted: Sun Jan 04, 2009 1:03 am
by allspiritseve
pytrin wrote:What you're describing in this example is already the result object. I'm talking about Table gateway methods that compose the queries to populate the data in those row objects
Ah, I get it now. Looks good.

I've sort of chosen to let my mappers be responsible for themselves and all immediate relationships, even if that means some duplicate code. I may end up abstracting my SQL into TDG objects at some point, but for now the mappers are doing straight SQL. It's all behind the interface though, so it doesn't matter how the queries get done, as long as the right objects are returned.

Re: Building complex domain models

Posted: Sun Jan 04, 2009 1:10 am
by Eran
It's all behind the interface though, so it doesn't matter how the queries get done, as long as the right objects are returned.
Generally I agree. I'm just looking for more ways to prevent duplication and increase development speed :)