I'm designing a system using Domain-Driven design principals.
I have an aggregate named Album
.
It contains a collection of Track
s.
Album
instances are created using a factory method named create(props)
.
Rule 1: An Album
must contain at least one Track
.
This rule must be checked upon creation (in Album.create(props)
).
Also, there must a method named addTrack(track: Track)
so that a new Track
can be added after the instance is created. That means addTrack(track: Track)
must check the rule too.
How can I avoid this logic code duplication?

- 470
- 6
- 18
1 Answers
Well, if Album
makes sure it has at least one Track
upon instantiation I don't see why addTrack
would be concerned that rule could ever be violated? Did you perhaps mean removeTrack
?
In that case you could go for something as simple as the following:
class Album {
constructor(tracks) {
this._tracks = [];
this._assertWillHaveOneTrack(tracks.length);
//add tracks
}
removeTrack(trackId) {
this._assertWillHaveOneTrack(-1);
//remove track
}
_assertWillHaveOneTrack(change) {
if (this._tracks.length + change <= 0) throw new Error('Album must have a minimum of one track.');
}
}
Please note that you could also have mutated the state first and checked the rule after which makes things simpler at first glance, but it's usually a bad practice because the model could be left in an invalid state if the exception is handled, unless the model reverts the change, but that gets even more complex.
Also note that if Track
is an entity, it's probably a better idea not to let the client code create the Track
to preserve encapsulation, but rather pass a TrackInfo
value object or something similar.

- 42,889
- 6
- 74
- 90
-
I see. About not letting the client code create the tracks, you suggest letting the client code create value objects but not entities like track? Why? Can I create value objects inside the factory too? @plalx – Amirhosein Al Oct 04 '20 at 16:32
-
Also, aggregates like Album, Track, User, etc. usually have all of their properties updated (almost all of them). Should I create an instance method for setting each of these? Or just use a simple setter? @plalx – Amirhosein Al Oct 04 '20 at 16:36
-
The reason is that if the client can modify `Track` outside the context of `Album` then invariants can't be protected. For instance, if you can't have 2 tracks with the same name the client could violate the rule: `track1 = new Track('foo'); album = new Album(track1); album.addTrack(new Track('track2')); track1.changeName('track2'); //rule violated` – plalx Oct 04 '20 at 16:41
-
Well, DDD tactical patterns may get in the way of CRUD. If you only do CRUD then don't expect to have a very interesting and behavior-oriented model. If the business process is "update"/"change track info", then you could do `album.changeTrackInfo(trackId, newTrackInfo)`. Note that perhaps `Track` is also a value object from the model's perspective if you don't care about the lifecycle of a specific track. – plalx Oct 04 '20 at 16:45
-
1Got it. I think `Track` is an entity since I'm gonna store data about each track that's listened to by the user. Thanks a lot @plalx. I think I get it now. – Amirhosein Al Oct 04 '20 at 16:51
-
Also note that DDD is all about context. Listening to music & browsing available contents is very different from managing the system's albums & tracks. – plalx Oct 04 '20 at 17:09
-
I think my system has quite a few rules. I would appreciate it if you could give me a bit of your time. I have a few questions that are not specifically related to this post. @plalx – Amirhosein Al Oct 04 '20 at 18:00
-
Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/222493/discussion-between-plalx-and-amirhosein-al). – plalx Oct 04 '20 at 18:18