0

I've been learning about DDD and just wanted to make sure I understood something correctly. I've read in various places that aggregate roots properties should not directly reference entities from other aggregate roots. So let's say we have 2 aggregate roots: Student, and Course.

Originally, I would have designed my models like this:

public class Student
{
    private int Id {get;}
    private readonly List<Course> _courses = new();
    public IReadOnlyCollection<Course> Courses => _courses;

    public void Enroll(Course course)
    {
        _courses.Add(course);
    }
}
public class Course
{
    private int Id {get;}
    private readonly List<Student> _students = new();
    public IReadOnlyCollection<Student> Students => _students;

    public void AddStudent(Student student)
    {
        _students.Add(student);
    }
}

After learning about not referencing entities from other aggregates, I instead ended up with collection of ids instead:

public class Student
{
   private int Id {get;}
   private readonly List<int> _courses = new();
   public IReadOnlyCollection<int> Courses => _courses;

   public void Enroll(Course course)
   {
       _courses.Add(course.Id);
   }
}

Is that correct? It feels somewhat weird and counter intuitive. I also don't like that now there is no typed guarantee that my collections elements will be of the expected type. I could mistakenly change my code to do _students.Add(course.Id) and the error would be hard to catch. Any advice to avoid that?

MyUsername112358
  • 1,320
  • 14
  • 39

1 Answers1

0

I also don't like that now there is no typed guarantee that my collections elements will be of the expected type.

The standard answer here: model the entity identifier itself as a type - aka "value object".

public class StudentId {
  // ...
}

public class Student {
  StudentId id;
  // ...
}

TADA - you have type safety again. As an added bonus, the value object also reduces the amount of code directly coupled to the in memory representation, reducing the amount of work you need to do if you later discover that the identifiers should be strings.

See Domain Driven Design (Evans, 2003) Chapter 5.


Is that correct?

Maybe?

It is often the case that a "collection of relationships" will be modeled as a collection of identifiers, with the expectation that you can join everything together when you create your reports.

Does that mean we should have a collection of course identifiers in Student? or a collection of student identifiers in Course? Or something else? Any of the above is possible, depending on the needs you are attempting to serve.


So all my entities will need a corresponding "EntityId" class and file?

Not necessarily - alternatives include nesting the Identifier class within the definition of the type, or using a specialization of a generic Identifier[T].

public class Student {
  Identifier[Student] id;
  // ...
}

Horses for courses.

VoiceOfUnreason
  • 52,766
  • 5
  • 49
  • 91