I had a few questions around value objects in DDD.
I had a class ReportDefinition
(basically a schema for a specific report that users can create) with the following properties (in Java):
UUID id;
UUID ownerId;
UUID organizationId;
ReportDefinitionName name;
ReportTitle reportTitle;
ReportDescription reportDescription;
Instant createdAt;
Instant lastModifiedAt;
As you can see, there everything is a value object. So far, all the value objects in the systems have been wrappers around a single primitive (Strings for the most part). i.e. to get the report title as a string, I would do reportDefinition.reportTitle().title()
.
Yesterday I needed to add another property, ReferenceType
. I came to the realization that reportTitle
, reportDescription
and the referenceType
should probably be grouped together another value object, ReportDetails
. I can see the need for referenceType
to still be a value object, but not sure if I should change title
and description
to simple strings within the ReportDetails
?
At the moment, ReportTitle
and ReportDescription
do some simple validations (i.e. report title must be between 1 and 60 characters, etc), but that's about it. If I made them strings, I could still do the basic validation checks within ReportDetails
.
Another question that arises is whether this might lead to violation of the law of demeter. i.e. it's like that the application layer will need to call reportDefinition.reportDetails.title().title()
which is starting to get a bit crazy.
And finally, given that value objects should be immutable, I'm guessing if I wanted to update the report details, I might need methods like ReportDetails updateTitle(title)
that returns a new instance of ReportDefinition
. I was wondering who should actually call that method? i.e. should it be a protected method within ReportDetails
and called via a void updateTitle()
method on the ReportDefinition
? Or is it fine for the application layer to call reportDetails.updateTitle()
and then pass the result to an updateReportDetails()
method on ReportDefinition
?
Part of the reason why it made sense to split them out into a seperate object was because a Report
will need the information in ReportDetails
when being created (whereas the rest of the information in ReportDefinition
is irrelevant and deals with other concerns).
The immutability is an artefact of following DDD and not wanting consumers of the class to modify ReportDetails
simply on account of having a reference to it. A business case is that ReportDetails
can only be change when ReportDefinition
is in a draft state (not shown above) whereas the other information in ReportDefinition
can always be changed.
2 Answers 2
Immutable is a wonderful thing, but the main reason why is not because DDD said so, because of threads, or even because it protects against nonsense. No the main reason is because immutable value objects are easier to read. Easier to reason about.
But they come at a cost. You only get one moment in time to build them.
I really don't understand why you're going through 4 layers to get a title when report.getTitle()
should be the end of it.
-
There's a distinction between a
ReportDefinition
and aReport
. AReportDefinition
is a template for a specific type ofReport
, alongside some other information (i.e. theReportDefinitionName
is the name of theReportDefinition
and nothing to do withReports
).TheReport
itself will have a getter to get the title. Think ofReportDetails.ReportTitle
as being a templated string. A specific instance of aReport
has some template variables that are used to resolve the title.NRaf– NRaf06/01/2018 04:03:48Commented Jun 1, 2018 at 4:03 -
Still don't understand.
reportDefinition.getTitle()
gets the report definition's title. I can't see whatreportDefinition.reportDetails.title().title()
is getting you. What's wrong withreportDefinition.getTitle().toString()
?candied_orange– candied_orange06/01/2018 19:29:52Commented Jun 1, 2018 at 19:29 -
A
ReportDefinition
has a name (reportDefintion.getName()
) but thetitle
isn't a direct child ofReportDefinition
, it's part of the schema / template for aReport
thatReportDefinition
has. That schema has a number of other fields as well that are all used to help build aReport
. TheReportDefinition
has other fields which don't relate to buildingReports
(i.e. isDraft, createdTime, ownerId, etc), which is why I've created theReportDetails
object.NRaf– NRaf06/01/2018 23:24:09Commented Jun 1, 2018 at 23:24 -
With that said, I can do what you suggested, however I feel that
reportDefinition.getReportDetails().getTitle()
might make the API a bit clearer. i.e. having a methodgetName()
andgetTitle()
on theReportDefinition
might be confusing to a user (what's the difference between the two?). Don't mind thetoString()
, I think that's better than having.title()
at the end of the chain.NRaf– NRaf06/01/2018 23:24:12Commented Jun 1, 2018 at 23:24 -
1Then in those cases where it's passed in you don't have a Demeter problem. You can just use
reportDetails.title()
. But remember Demeter is not just a dot counting exercise.candied_orange– candied_orange06/02/2018 09:55:21Commented Jun 2, 2018 at 9:55
It looks like you have quite a complex object there. Too complex for a specific answer. But I would make the following observations:
- It seems like a bad idea to enforce business rules via immutability.
- Having all you rules in property setters is also somewhat confusing.
- Add some methods to the top level object ie. Report.ChangeTitle(string title) and hide the complexity of report.reportDetail.reportTitle.title behind them.
An object which its impossible for the programmer to break rules with, due to immutability, setters, private methods or clever tricks with builders and interfaces is a wonderful thing to see and use.
But its much harder to achieve than an object which is impossible for the user to break rules with.
ReportDetails
is the first enhancement, immutability is the second. Have you considered whether the benefits you derive from these enhancements outweigh the additional complexity that they bring to your project?