In Domain Driven Design, there seems to be lots of agreement that Entities should not access Repositories directly.
Did this come from Eric Evans Domain Driven Desi
At first, I was of the persuasion to allow some of my entities access to repositories (ie. lazy loading without an ORM). Later I came to the conclusion that I shouldn't and that I could find alternate ways:
Vernon Vaughn in the red book Implementing Domain-Driven Design refers to this issue in two places that I know of (note: this book is fully endorsed by Evans as you can read in the foreword). In Chapter 7 on Services, he uses a domain service and a specification to work around the need for an aggregate to use a repository and another aggregate to determine if a user is authenticated. He's quoted as saying:
As a rule of thumb, we should try to avoid the use of Repositories (12) from inside Aggregates, if at all possible.
Vernon, Vaughn (2013-02-06). Implementing Domain-Driven Design (Kindle Location 6089). Pearson Education. Kindle Edition.
And in Chapter 10 on Aggregates, in the section titled "Model Navigation" he says (just after he recommends the use of global unique IDs for referencing other aggregate roots):
Reference by identity doesn’t completely prevent navigation through the model. Some will use a Repository (12) from inside an Aggregate for lookup. This technique is called Disconnected Domain Model, and it’s actually a form of lazy loading. There’s a different recommended approach, however: Use a Repository or Domain Service (7) to look up dependent objects ahead of invoking the Aggregate behavior. A client Application Service may control this, then dispatch to the Aggregate:
He goes onto show an example of this in code:
public class ProductBacklogItemService ... {
...
@Transactional
public void assignTeamMemberToTask(
String aTenantId,
String aBacklogItemId,
String aTaskId,
String aTeamMemberId) {
BacklogItem backlogItem = backlogItemRepository.backlogItemOfId(
new TenantId( aTenantId),
new BacklogItemId( aBacklogItemId));
Team ofTeam = teamRepository.teamOfId(
backlogItem.tenantId(),
backlogItem.teamId());
backlogItem.assignTeamMemberToTask(
new TeamMemberId( aTeamMemberId),
ofTeam,
new TaskId( aTaskId));
}
...
}
He goes on to also mention yet another solution of how a domain service can be used in an Aggregate command method along with double-dispatch. (I can't recommend enough how beneficial it is to read his book. After you have tired from end-lessly rummaging through the internet, fork over the well deserved money and read the book.)
I then had some discussion with the always gracious Marco Pivetta @Ocramius who showed me a bit of code on pulling out a specification from the domain and using that:
1) This is not recommended:
$user->mountFriends(); // <-- has a repository call inside that loads friends?
2) In a domain service, this is good:
public function mountYourFriends(MountFriendsCommand $mount) { /* see http://store.steampowered.com/app/296470/ */
$user = $this->users->get($mount->userId());
$friends = $this->users->findBySpecification($user->getFriendsSpecification());
array_map([$user, 'mount'], $friends);
}