3

I have a rich model, where e.g. one model A depends on many related entities/value objects. Therefore we have methods in A for retrieving collections of related objects: getFoos(), getBars() and so on.

At one point, one or many of these related collections should not be fetched eagerly, and, moreover, should be fetched using pagination.

I do not want to 'pollute' the model with methods like getFoosPage(from, to, size). This is not part of the business, it's part of the viewing problem.

How should I solve the pagination?

Next, sometimes I need to get just A with Foos, but not Bars. Should I have a method in my repo saying: getAWithFoos()? I do not like that are returned A will be only partially populated (no boos), and you can not tell that just by inspecting the model.

For now, I am thinking in building a 'query' model, where I would have different classes for scenarios I need, like 'AWithFoos` that contains A and related Foos and so on. Does that make sense?

4

The "query model" you mention totally makes sense, actually there's a whole approach based on that (CQRS). Query side would expose Read Model Facades for all the fancy tailored display models you need, and command side has Repositories for business transactions through Aggregates.

In a traditional non-CQRS DDD app, I wouldn't be shocked to see paging and filtering parameters in repo methods though, since they are also your query objects.

You can also have a look at the Rules of Effective Aggregate Design by Vaughn Vernon. Through smaller Aggregates and more careful domain design, it addresses the kind of entity loading and performance issues you seem to experience.

  • So it would be OK to create various different Aggregates depending on what related collection I need? – lawpert Oct 28 '14 at 14:13
  • 1
    Yes, except they aren't Aggregates, just read models. Aggregate is the unique checkpoint through which all modifications to a group of related entities must go. Variations and hybrids of these entities intended for readonly querying can exist alongside, but they aren't Aggregates. – guillaume31 Oct 28 '14 at 14:25
  • Got it. What if I need to update (that is a command, right) just bar value object under the A aggregate? I would not fetch all foo collection for that, since it is not used in this particular use case/command. Would I have different aggregates for commands? – lawpert Oct 28 '14 at 15:03
  • Depends. One rule is that (read RoEAD for more details) the contours of an Aggregate should approximately match the scope of a domain transaction. Larger than that means concurrency and performance problems, smaller means domain rules that should belong together are scattered across multiple Aggregates which forces you to have awkward inter-Aggregate communication. – guillaume31 Oct 28 '14 at 15:25
  • Bottom line is that if updating A.Bar only obeys A's rules and invariants and has nothing to do with a Foo, then there's no reason that Foo wouldn't have its own Aggregate. – guillaume31 Oct 28 '14 at 15:27
1

You should consider the Lazy Load pattern If you wish to fetch items only per request

As for the need of the view. I used a different solution. I noticed that on sophisticated views I need to load lots of entities which then in turn loaded their children. Way too much work on the DB / process memory. So I created a Report object that basically extend the object that can populate result set from SQL result (in my case it was ZF2 ResultSet). Then all I had to do was:

  1. Set the SQL (left join or whatever, I call views that hide complexity)
  2. Initiate the object (passing adapter)
  3. Work on the result set

Its a simple one query, read only solution.

<?php

namespace MyProj\Mapper\Reports;

use Zend\Db\Adapter\Adapter;
use Zend\Db\ResultSet\ResultSet;

class AccountsWithGroupsUsersCount extends ResultSet
{
    protected $sql = 'SELECT * FROM v_accounts_with_groups_and_users_count';

    /**
     * Execute the script and initilize the users list
     * @param int $accountId
     * @param Adapter $dbAdapter
     */
    function __construct(Adapter $dbAdapter)
    {
        parent::__construct();
        $stmt   = $dbAdapter->query($this->sql);
        $result = $stmt->execute();
        $this->buffer()->initialize($result);
    }

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.