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?
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.
5
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:
- Set the SQL (left join or whatever, I call views that hide complexity)
- Initiate the object (passing adapter)
- Work on the result set
Its a simple one query, read only solution.
<?php
namespace MyProjMapperReports;
use ZendDbAdapterAdapter;
use ZendDbResultSetResultSet;
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);
}
}