Domain Model Association Lazy Loading

509 Views Asked by At

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!

1

There are 1 best solutions below

6
On

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.