1

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.

1 Answer 1

0

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?

6
  • But if another application removes a Guardian by accessing GuardianRepo directly, doing it by the AR or Service does not matter because they are gone right? Because Repos does not have logic but just CRUD. Aug 18, 2018 at 9:42
  • @doesnotmatter the service would check both repos and not allow it.
    – Ewan
    Aug 18, 2018 at 9:50
  • I'm still not confident about removing a required entity from constructor. Doing so will make operations such as (just for example) removeLastGuardian() invalid if there's no guardians loaded. If resorting again to Service, wouldn't that make my ARs Anemic Model? Aug 18, 2018 at 10:09
  • then you should just have a single AR as I suggest
    – Ewan
    Aug 18, 2018 at 10:20
  • Yes I think that is valid answer. Guardian has a different context here. Last question: for the technical/implementation side, can I reference another repo inside another? Or can I just fetch from required tables without using other repo? Aug 18, 2018 at 10:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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