Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DDD - Problems in constructor and methods in entities design

I have a problem with regards to entity design. As I've read, when you design a DDD entity, the constructor should contain the values needed for an entity to "exist". For example, in the domain I am working on, a Class entity cannot exist without a Section and a Level:

public class Class
{
    public Class(short id, string section, Level level)
    {
        ID = id;
        Section = section;
        Level = level;
    }
    //
    // Properties
    //
    public short ID { get; private set; }
    public string Section { get; private set; }
    public Level Level { get; private set; }
    //
    // Methods
    //
    public static IList<Class> GetClassesByTeacher(short teacherID)
    {
        List<Class> classes = new List<Class>();
        classes.Add(new Class(1, "a", null));
        classes.Add(new Class(2, "b", null));
        classes.Add(new Class(3, "c", null));
        return classes;
    }
}

Here Level is also an entity. As I am not yet finished in the design, Level's contructor might also contain an entity SchoolYear. What bothers me is for me to call GetClassesByTeacher method, I need to instantiate a Class along with other entities (Level, and also SchoolYear, needed in Level's constructor).

Is this the correct? I think it's bothersome when I just want to call the method. Are there other ways? I considered making it static but others said testability would suffer. I'm not sure if CQRS is one of the solution for what I want to do as I haven't yet read too much about it, but if it is, are there any other techniques aside from CQRS I can employ, or is it when going DDD, this is how it really is? Or is my entity design incorrect?

like image 950
g_b Avatar asked Mar 19 '23 09:03

g_b


2 Answers

You should reconsider you domain model, as you actually say it seems there something wrong.

I may refer to Single Responsibility Principle (SRP) in which any class should have one one reason to change. In your example, what happens if we added a new field to 'Class', we will modify 'Class' it self and is right, but what happens if we decide that the listing should be on reversal order, or if you need a new type of list taking into account only interim teachers... you should change 'Class' but 'Class' has nothing to do with listings.

To answer you question, you might take a look at Repository pattern. Repositories are where you could ask for these type of listings.

I might split Class into two:

  • one for 'Class' model
  • other for 'Class' repositories

In summary:

public class Class
{
    public Class(short id, string section, Level level)
    {
        ID = id;
        Section = section;
        Level = level;
    }
    //
    // Properties
    //
    public short ID { get; private set; }
    public string Section { get; private set; }
    public Level Level { get; private set; }
}



public class ClassRepository
{
    private IList<Class> contents;

    //
    // Methods
    //
    public IList<Class> GetClassesByTeacher(short teacherID)
    {
        List<Class> classes = new List<Class>();
        for (Class elem: contents) {
            if (elem.getTeacher().equals(teacherID) {
                classes.Add(elem);
            }
        }
        return classes;
    }
}

repository = new ClassRepository;
level1 = new Level();
repository.Save(new Class(1, "a", level1));
repository.Save(new Class(2, "b", level1));
repository.Save(new Class(3, "c", level1));

result = repository.GetClassesByTeacher(1);

There are other details like using a ClassRepositoryInterface and implement with an InMemoryClassRepository, you will miss also Teacher information in class, as long as one one teacher drives the class, if not you might change how to filter by teacher, etc, etc.

I'm not a Java developer and the code will not compile but I expect you get the idea.

like image 60
Xavier Fornés Avatar answered Apr 28 '23 08:04

Xavier Fornés


The GetClassesByTeacher method does indeed belong in some kind of repository or service class. Your Class entity is meant to represent an instance of that thing in the real world. An entity is not meant to provide instances (say, from some underlying persistence) - they are only meant to represent them. A ClassRepository would be a way to provide instances of the Class entity into your domain.

You also mentioned that Class cannot exist without a Level. You are speaking about aggregates here. There is a lot of DDD material online with regards to designing aggregates. Here are a couple:

  • DDD: Aggregates and Aggregate Roots
  • DDD: The Aggregate And Aggregate Root Explained

EDIT:

When an entity needs another entity to exist, should it always be an aggregate?

No, just because entity A depends on entity B's existence, it doesn't mean that entity A needs to belong to entity B's aggregate.

As now I have a model in which many entities (like Class, Level, Subject, Adviser, Teacher, etc.) exists only in a certain SchoolYear (an entity). Is it OK for an aggregate to get that large?

An aggregate this large could result in performance and consistency issues.

  • Why performance? You could be loading a huge graph of data in memory for some aggregate root. You most likely only need to work on a fraction of that aggregate for whatever unit of work is occurring and not all the entities involved.
  • What consistency issues? The larger the aggregate, the more likely data can be changed in another part of the system while it has been retrieved into memory. When saving, data loss can occur.

Vaughn Vernon covers exactly these problems in his three-part Effective Aggregate Design series. It's a bit daunting if you aren't familiar with DDD lingo, but I highly recommend this read!

Also, by what you said, the Entity should not use the repository. If some business logic in the Entity needs database access for it to be processed, should it directly use DAL?

Entities should not know anything about repositories or services (or anything else, for that matter). They should not have any dependency on any other 'layer' in the system. Everything can know about your entities, but your entities shouldn't know about anything else. Provide me with an example of why you would want an entity to call on a repository and maybe I can provide a better answer.

like image 20
Dave New Avatar answered Apr 28 '23 08:04

Dave New