Domain driven design: How to deal with complex models with a lot of data fields?
Asked Answered
A

2

15

Well I am trying to apply domain driven design principles for my application, with a rich domain model that contains both data fields and business logic. I've read many DDD books, but it seems that their domain models(called entities) are very simple. It becomes a problem when I have a domain model with 10-15 data fields, such as the one below:

class Job extends DomainModel{

    protected int id;
    protected User employer;
    protected string position;
    protected string industry;
    protected string requirements;    
    protected string responsibilities;    
    protected string benefits;
    protected int vacancy;
    protected Money salary;
    protected DateTime datePosted;
    protected DateTime dateStarting;
    protected Interval duration;   
    protected String status;
    protected float rating;  

    //business logic below 
}

As you see, this domain model contains a lot of data fields, and all of them are important and cannot be stripped away. I know that a good rich domain model should not contain setter methods, but rather pass its data to constructor, and mutate states using business logic. However, for the above domain model, I cannot pass everything to the constructor, as it will lead to 15+ parameters in constructor method. A method should not contain more than 6-7 parameters, dont you think?

So what can I do to deal with a domain model with a lot of data fields? Should I try to decompose it? If so, how? Or maybe, I should just use a Builder class or reflection to initialize its properties upon instantiation so I wont pollute the constructor with so many arguments? Can anyone give some advice? Thanks.

Acetophenetidin answered 11/10, 2015 at 16:55 Comment(0)
L
15

What you've missed is the concept of a Value Object. Value objects are small, immutable objects with meaning in the respective domain.

I don't know the specifics of your domain, but looking at your Job entity, there could be a value object JobDescription that looks like this:

class JobDescription {
    public JobDescription(string position, string requirements, string responsibilities) {
        Position = position;
        Requirements = requirements;
        Responsibilities = responsibilities;
    }

    public string Position {get;}
    public string Requirements {get;}
    public string Responsibilities {get;}
}

This is C# code, but I think the idea should be clear regardless of the language you are using.

The basic idea is to group values in a way that makes sense in the respective domain. This means of course that value objects can also contain other value objects.

You should also ensure that value objects are compared by value instead of by reference, e.g. by implementing IEquatable<T> in C#.

If you refactor your code with this approach, you will get fewer fields on your entity, so using constructor injection (which is highly recommended) becomes feasible again.


Further notes regarding your example code that are not directly connected to the question:

  • The domain model is the whole thing, an entity is part of it. So your base class should be called Entity and not DomainModel.

  • You should make the fields of your class private and provide protected accessors where required to maintain encapsulation.

Loveinamist answered 12/10, 2015 at 8:15 Comment(5)
I see, yeah I think I should start to implement more value objects, like the Money value object I already have. Thanks for the suggestion, I will start by composing data fields by smaller value objects, and use value objects in each entity. I do know protected fields sometimes break encapsulation, so I should switch to private in most cases, thanks too. I am not sure about the domain model vs entity thing though, to my understanding a domain model is an entity, or that an entity is a domain model with an identity, is this correct?Acetophenetidin
@LordYggdrasill A domain model is the whole set of "things" that have meaning in your domain, e.g. entities, value objects, etc. See for example this article, the diagram shows a sample domain model that contains many classes.Loveinamist
I see, thanks. I understood this better after reading some articles about aggregate root.Acetophenetidin
Well sorry I am back with another question. I have decomposed the job entity into a few value objects: JobDescription, JobEnvironment, JobOrganization, etc. Now I am facing another issue. I think I should also put business logic to each of these value objects, but then there is a problem. The job entity by itself becomes quite anemic(most of business logic now delegated to the value objects inside it), and to call these methods become a bit more cumbersome. Instead of Job.countHoursOfWork(), I now need to call Job.getEnvironment().countHoursOfWork().Acetophenetidin
So is this the supposed behavior? The I need to call Job.getEnvironment().countHoursOfWork()? I think this makes the API a bit harder to follow. Maybe I should provide a method on Job too which delegates the call to JobEnvironment?Acetophenetidin
Q
2

There's an awful lot going on in your Job domain model object - it seems to mix a huge number of concerns, and (to me at least) suggests a number of bounded contexts, some of which are easy to discern for the sake of making an example.

  1. Remuneration (pay, benefits)
  2. Organisational position (reporting line)
  3. Person spec (skills)
  4. Job specification (responsibilities)
  5. etc.

When you consider the things that interact with your 'Job' model, are there any that need to inspect or mutate BOTH the Salary property and the Industry property, for example?

Without knowing the full nuances of the domain, the Salary you get for holding a position and the Industry you work in are not really connected, are they? Not a rhetorical point; these are the questions you NEED to ask the domain experts.

If they DON'T have any interaction then you have identified that these two things exist in two different BOUNDED CONTEXTS. The Salary side has no need of any interaction with the Industry side and vice versa, and even if they did, do they need to be held as state in the same process at the same time?

Think about the lifecycle of how a person becomes an employee; a person applies for a job. The job has a specification, salary range. The person attends an interview. The hirers offer the person the position. The person accepts. The person is now an employee, not a candidate any longer. The new employee is now accruing holiday and benefits and has a start date etc.

DDD teaches us that a single, unified view of the world rarely serves ANY of the concerns correctly. Please explore BOUNDED CONTEXTS - your software will be much more pliable and flexible as a result.

Quinate answered 13/10, 2015 at 19:18 Comment(1)
Yeah you have a point, in this way my Job domain model is more like an aggregate root, it may also contain other fields such as users who have applied to the job. Id definitely consider refactoring some fields into smaller domain objects(I think they will be, value objects? since they dont have identity outside of the Job aggregate root).Acetophenetidin

© 2022 - 2024 — McMap. All rights reserved.