2

I'm currently facing the problem of handling the associations between the entities in the aggregate.

Consider following example:

Right now, we have a ,,User" Entity, which is also my aggregate root. He can have exactly one ,,Product" associated to him , and many ,,Aliases". What I need currently is the ability of retrieving the related ,,Product" and ,,Aliases" on-demand within the Domain Model. User is being created by UserFactory, which can be used separately or also used within UserRepository when creating Entities from data in persistence.

<?php
class User {
   private $id;

   private $aliases;
   private $product;

   public function isEligibleForCompanyPromotion() {
      //I need a good way to populate this without a performance bottleneck. 
      $product = $this->product;
      [.. code comparing things between User and Product begins ..]
   }     

}

The reason to this is because our business logic ( as you can see in example ) relies a lot on associations between objects and I suppose this logic related to associations between objects should be done within the Domain Model ( or maybe this should be moved out? )

I considered following solutions:

1) Just load everything ( the associated Product and Aliases ) when the User is being constructed.

Pros:

  • Works?

Cons:

  • BIG performance issue ( and unfortunately I'm working on live high-load system, so this can't be passed up upon )

2) Inject the relationship repository into the domain model

  private $productRepository;
  private $aliasesRepository;
  [...]

}

Pros:

  • Works ..

Cons:

  • Defeats ( imo ) the purpose of DDD , with User being required to have the repositories inside ( so domain model is coupled to persistence.. )

3) Delegate the association-related logic out of Domain Model ( maybe to Domain Services and Policies? )

Pros:

  • Also works

Cons:

  • Actually I feel like this leads to Anemic Domain Models , because majority of our business logic is based on the relations of the objects, so a lot of code would be moved out of Domain Objects which kind of makes no sense.

4) Create a specialized Relationship object for handling the relation:

<?php
class UserFactory { 

private $relationshipFactory;

public function createUser($persistenceData) {
    $user = new User();
    [.. creating of User, populating data etc .. ]
    $user->product = $this->relationshipFactory->createUserProductRelationship($user);
}

}


class UserProductRelationship {

    private $user;
    private $product;
    private $productRepository;

    [.. productRepository is being injected within relationshipFactory, and user is  provided ..]

    public function loadProduct() { 
      [.. load the product based on the relationship criterias ..]
    }

    [.. calls to this object are proxied to lazy-loaded product via __call, __get etc. )
}

Pros:

  • Also works

Cons:

  • Circular dependency ( User is required to have the UserProductRelationship inside and relies on it. UserProductRelationship is required to be provided with the User entity. Combining those two classes would lead to scenario #2 ).

Maybe I didn't understand something properly.. should anyone have a suggestion, I'd be happy to hear about it.

Thanks!

user1009783
  • 106
  • 1
  • 2

1 Answers1

1

Introduce a specification object if some responsibility does not belong to any object or the domain object needs too many unrelated data to solve a problem.

What you need is a EligibleForCompanyPromotionSpecification like:

class EligibleForCompanyPromotion {
    private $productRepository;
    private $aliasesRepository;

    public function isSatisfiedBy(User user) {
       //I need a good way to populate this without a performance bottleneck. 
       $product = $productRepository.find(user.productId);
       [.. code comparing things between User and Product begins ..]
}   

class User {
   private $id;

   private $aliasesId;//only identifiers needed
   private $productId;//only identifiers needed 

In this case, domain logic does not leak to application layer and we don't have to inject repositories into User.

Yugang Zhou
  • 7,123
  • 6
  • 32
  • 60
  • So, more or less, this is a concept from answer number 3 ? Should I delegate comparisions between two domain objects out of domain objects to such Policy/Service classes? – user1009783 Dec 31 '13 at 11:28
  • @user1009783 The Specification is also a domain concept. It is often used when you want to perform some validation and keep the validation subject decoupled with validation rules(which usually introduces unnecessary dependencies). Please refer to [Specification Pattern](http://en.wikipedia.org/wiki/Specification_pattern). – Yugang Zhou Dec 31 '13 at 11:44
  • Ok. Understood. How about retrieving data inside the said entity ( like in example, but consider we do not want to do it in validation ) ? From what I understand, Repository should be doing it. However, loading whole object graph ( in my case : User and his aliases and his product at once ) is uneffective, I need a way to lazy-load. What I have came up to mind is implementation of 4), creating ,,Proxy" objects implementing the Related entity interface, but tied to repositories and loading the actual entity upon first usage. Or maybe I should just avoid relationship in domain object at all? – user1009783 Dec 31 '13 at 14:58
  • @user1009783 Lazy loading is somtimes considered as a code smell which indicates the domain model is misused for query. You could have a look at this [question](http://stackoverflow.com/questions/20820302/is-depending-on-lazy-loading-a-code-smell). Usually, you have to inject repository or service into the domain model if you want to retrieve data inside the entity. But this is argued as an [antipattern](http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities). The specification, on the other hand, could alleviate these problems :) – Yugang Zhou Dec 31 '13 at 15:10
  • Don't you feel like this makes Domain Model a bit anemic? I mean, what could possibly such Domain Model do besides updating itself's state, which is not really enough to do meaningful business processes? I suppose someone could argue this should be delegated to Services in order to orchestrate the changes between multiple entities? This might work. Although I still have a problem at heart with it , because I feel like my entities.. aren't coupled anymore, and cannot be used as a relationship without that service. – user1009783 Dec 31 '13 at 15:19
  • Rich domain model does not mean putting everything into entities. Responsibility determines where the code belongs. – Yugang Zhou Dec 31 '13 at 15:45