DDD Aggregate Root – Getting Reference to Another Aggregate Root

aggregatedomain-driven-designinvariants

I have 3 ARs:

  • Student
  • Guardian

Business Rule:

  • Student should have at least 1 authorized Guardian
    Entities should not be in an invalid state right?

Right now I enforced the invariant in my Student constructor:

public function __construct(StudentId $id, Name $name, Guardian ...$guardians) {

  if ( $this->hasNoAuthorizedGuardian(...$guardians) ) {
      throw new NoAuthorizedGuardianException();
  }

  // initialized fields
}

However, while creating the repository part for my Student AR, I encountered a problem of fetching the "required" guardians for it.

class StudentRepository {
    public function getById(StudentId $id): Student {
        // other lines omitted for brevity
        return new Student($id, $name, ...$guardians);
    }
}

I came up with these questions in mind:

  • Should I reference GuardianRepository inside StudentRepository? I feel like the answer is no, but I'm new to DDD so it might be valid

  • Should ARs be allowed to have reference to Repositories instead? I feel like the answer to this is also no, but like I said I'm new to DDD so this might be valid.

  • Did I enforce my invariant properly? Or should I put it in Domain Service so that I don't need to fetch the related entities in a single Repository?

Doing the second approach will let me do this:

class StudentService {

    public function getById(StudentId $id) {
        $student = $this->studentRepo->getById($id);
        $guardians = $this->guardianRepo->getByStudentId($id);

        $student->guardians(...$guardians);

        return $student;
    }

}

Cons of doing that though is my entities will become an Anemic Domain Entity since I transferred the invariant to another layer.

Another possible solution I came up with is Lazy Loading but I'm not sure how to do that including when and how to fetch the related entities.

TL;DR: If your root entity requires a reference to another root entity, how can you get it?

EDIT:

For clarification, I did not mention that Guardian has its own entities like Contacts and some other Value Objects. I think it's enough for it to be considered an AR because it should be the one that manage them.

Student-Guardian reference here is a Bounded Context I guess that I just learned now.

Best Answer

It's totally fine for Entities to be in an 'invalid' state according to business rules such as 'Students must register a guardian or we cant legally enrol them'

These rules are subject to change and exceptional cases. You want to flag such cases as breaking the rules, rather than make it impossible for your program to instantiate an object of that type.

In this case your application can simply reference both AR repositories, ARs can have a sudentId/guardianId property that you can look up, or you can have the relationship in a separate service.

Now, if the 'invalidness' is not simply a business rule, but inherent to the nature of the Entity, such as, I don't know, Cats have 0-4 Legs and Cat.Speed depends on the exact number. Then Leg Should be part of the Cat AR.

If a Cat has 5 Legs then Cat.Speed will cause the program will crash, If I change the number of Legs on a cat then its Speed should automatically update or I will get the wrong output from my program etc.

Putting Cat and Legs in separate ARs would be problematic, Cat.Speed would have to call the LegRepo before returning a result, I could add a Leg without updating the Cat's speed etc.

You will have to decide which case your Student and Guardian fall into. Should you have a single AR so you can have Student.RemoveGuardian(id) and enforce not allowing the removal of the last guardian.

Or would it be more sensible to have GuardianRemovalService.RemoveGuardian(studentId,guardianId) which can also enforce the rule, but allows the possibility of another application removing the guardian directly from the GuardianRepo without following the business rule?