Search code examples
c#asp.net-identitydomain-modelrich-domain-model

Understanding rich domain models and dependencies


I'm trying to get my head around rich domain models and how to build semantic functionality into domain entities, where the domain entities are not tightly coupled to objects that provide implementations for semantic behaviour

For example, I want to build a User entity into my domain model, but I want it's implementation to be driven by identity framework

class User
{
    public string Email { get; set; }
    ... All of the other IdentityUser properties...

    public void DisableUser()
    {
        ...behaviour to disable a user, most likely requires UserManager
    }

    public void AddToRole(Role role)
    {
        ... most likely requires RoleManager
    }
}

So now that I have a domain model that behaves according to the business rules, and is ignorant to persistence and implementation.

But how exactly are DisableUser() and AddToRole() supposed to work when they have no dependencies and aren't in any way coupled to UserManager and RoleManager?

  • Generally, what am I missing?
  • Should domain entities have dependencies on objects that provide behavior?
  • How should I decouple my domain model from implementation providers?

Solution

  • A well crafted domain model should have no dependencies on any other architectural layers or services. With respect, domain model objects should be (in my case) POCOs (Plain Old CLR Objects). Services and layers such as business logic or persistence layers should then depend on these objects and return instances of them.

    There are several keys to building a domain model that respects low coupling, high cohesion and persistence ignorance. In one statement, the secret to this is "write the code you wish you had".

    Domain Model Example

    public class Student
    {
        // Collections should be encapsulated!
        private readonly ICollection<Course> courses;
    
        // Expose constructors that express how students can be created.
        // Notice that this constructor calls the default constructor in order to initialize the courses collection.
        public Student(string firstName, string lastName, int studentNumber) : this()
        {
            FirstName = firstName;
            LastName = lastName;
            StudentNumber = studentNumber;
        }
    
        // Don't allow this constructor to be called from code.
        // Your persistence layer should however be able to call this via reflection.
        private Student()
        {
            courses = new List<Course>();
        }
    
        // This will be used as a primary key. 
        // We should therefore not have the ability to change this value. 
        // Leave that responsibility to the persistence layer.
        public int Id { get; private set; }
    
        // It's likely that students names or numbers won't change, 
        // so set these values in the constructor, and let the persistence 
        // layer populate these fields from the database.
        public string FirstName { get; private set; }
        public string LastName {get; private set; }
        public int StudentNumber { get; private set; }
    
        // Only expose courses via something that is read-only and can only be iterated over.
        // You don't want someone overwriting your entire collection.
        // You don't want someone clearing, adding or removing things from your collection.
        public IEnumerable<Course> Courses => courses;
    
        // Define methods that describe semantic behaviour for what a student can do.
        public void Subscribe(Course course)
        {
            if(courses.Contains(course))
            {
                throw new Exception("Student is already subscribed to this course");
            }
    
            courses.Add(course);
        }
    
        public void Ubsubscribe(Course course)
        {
            courses.Remove(course);
        }
    }
    

    Granted, this domain model object was written with Entity Framework in mind, but it's a far cry from the usual Entity Framework examples (which are anemic domain models by contrast). There are a few caveats that need to be considered when crafting domain model objects in this way, but Entity Framework will persist them (with a little jiggery-pokery), and you get a domain model object that defines a clean, semantic contract to layers that depend on it.