How to handle a many to many relationship with an ORM mapped to a Domain Model when there are no technical constraints

domain-driven-designefcoreorm

Domain Driven Design states that you should have a domain model, which reflects the ubiquitious language used by the domain experts. When using ORMs and many to many relationships I am use to doing this:

public class Student
{
    public Student() 
    {
        this.Courses = new HashSet<Course>();
    }

    public int StudentId { get; set; }
    [Required]
    public string StudentName { get; set; }

    public virtual ICollection<Course> Courses { get; set; }
}

public class Course
{
    public Course()
    {
        this.Students = new HashSet<Student>();
    }

    public int CourseId { get; set; }
    public string CourseName { get; set; }

    public virtual ICollection<Student> Students { get; set; }
}

However, with EF Core you must create a join class as described here: https://github.com/aspnet/EntityFrameworkCore/issues/1368. In the case of the above; I would have to create a class called: StudentCourse.

What is the most appropriate way to map an ORM to a Domain Model assuming there are no technical constraints like with EF Core were you must currently have a join class? My thinking is:

1) If the join table has state and behaviour then use a join class.

2) If the join table has a name that is recognised by domain experts then use a join class e.g. a Person could be eligible for loans – here Eligibility is the join class and is a term recognised by the domain. In the case of the above; StudentCourse is not recognised by the domain.

3) Else don't use a join class (like in the example above).

Best Answer

The overarching paradigm that DDD seeks to provide is one focused on the behavior of your system. Everything else is an implementation detail. As such, one does not model the domain according to any specific persistence mechanism, rather, models the persistence mechanism according to the domain. Including some sort of "join" object in your domain for the sake of easing persistence to an RDBMS creates an unnecessary coupling and pollutes/obfuscates your model with persistence concerns.

You specific problem is caused by mixing the data model that an ORM requires with the domain model that your application requires. There is no DDD solution to employ here other than to decouple the former from the latter above.

ORMs are a great tool (cohesive mechanism) for generating a necessarily anemic data model (methods cannot be serialized within reason) and mapping to/from a necessarily anemic data store. Put another way, an ORM only provides a more OO experience for developers and offers a (leaky) abstraction over a data store. That's all. That is the goal of every ORM (what does it stand for again?).

That said, there are two types of applications:

The overwhelming majority of systems are trivial. That is, they simply collect and persist data then turn around and display it back to the user. Trivial applications boil down to CRUD with a few rules sprinkled here and there regarding (mostly) the format/range of data being entered. In such systems it's not usually necessary to isolate a domain model (or isolate only small portions of it) because there are so few rules.

The second type of application is the one that truly benefits from an isolated domain. It is an application with lots of business rules. Often this kind of application defines many types of object that aren't persisted at all, or includes lots of Aggregates. This makes isolating a domain paramount. An ORM's data model can only truly provide the consistency boundaries an Aggregate requires through discipline (not structure) because all of the Entities are made available (and mutable).

Whether or not you have "seen" this kind of application is beside the point, and does not invalidate the fundamental principals of clean architecture. Every decision is a trade off. A good software architect understands that and moves accordingly. If the costs of creating an isolated domain model are not worth the benefits, move forward accordingly. But understand the trade off. That's what systems design is all about: finding the perfect balance between business concerns and technical concerns. It's about pragmatism. Leave idealism to academics and jr. developers, and utilitarianism to CEOs. We are here to provide balance. That is our value.

Related Topic