In Domain-Driven Design (DDD) we try to create a shared model between customers, developers and code. The value in such a model is that everyone speaks the same language, reducing the possibilities of miscommunication and thereby hoping to improve the quality of the software.
One of the implications of this strategy is that any model we create must actually be representable in code. That draws some requirements upon the actual programming language we’re using. For that reason, in a lot of DDD examples an object-oriented language is used, since those languages provide constructs, especially objects, that seem to map pretty well to the structure of a lot of domains. But doing DDD definitely doesn’t constrain you to using an OO language: I see people doing DDD in F# and Eric Evans himself refers to using Prolog for some specific domains in the blue book.
When doing DDD in an OO language, however, there seem to be some established patterns and practices on how to represent specific domain concepts in code, the most common probably being always mapping entities or value objects in the model to a class in code. I’ve seen this former pattern cause some problems from time to time, and I personally don’t always follow that one as strictly anymore.
Let’s take the Purchase Order example from the blue book. The problem is that a given purchase order (a domain entity) can only have a maximum total value. This total order value is determined by the PO’s purchase items (also entities), consisting of a quantity, price and a reference to the part. We model this using a Purchase Order aggregate which is responsible for maintaining this invariant. So how do we represent this in code?
A common way of doing this is providing the following API:
[TestMethod] public void when_changing_the_quantity_of_an_item_we_cannot_exceed_the_max_value() { Order order = CreateOrder(ofMaximumValue:10, ofCurrentValue:9); Part part = CreatePartOfValue(1); PurchaseItem item = order.NewItem(part, quantity:1); try { order.ChangeQuantity(item,newQuantity:2); } catch(MaximumOrderValueExceeded) { return; } Assert.Fail("MaximumOrderValueExceeded was not thrown when exceeding the maximum order value"); }
In which we have represented the order and its items as classes in our code. There is a problem with this particular solution, however: as an API consumer we get a reference to the PurchaseItem, which allows us to break the aggregate’s invariant by directly modifying the Quantity property on the PurchaseItem. This is undesirable, because someone less familiar with the code might actually do this. Of course, there are technical ways of preventing this, but in general those ways tend to clutter the code and it’s just not really necessary.
We can do better by changing the API as follows:
[TestMethod] public void when_changing_the_quantity_of_an_item_we_cannot_exceed_the_max_value() { Order order = CreateOrder(ofMaximumValue:10, ofCurrentValue:9); Part part = CreatePartOfValue(1); PurchaseItemId itemId = order.NewItem(part, quantity:1); try { order.ChangeQuantity(itemId,newQuantity:2); } catch(MaximumOrderValueExceeded) { return; } Assert.Fail("MaximumOrderValueExceeded was not thrown when exceeding the maximum order value"); }
Here we changed from passing around entities to passing around entity Ids, which means that we cannot invoke operations on PurchaseItems directly anymore (besides not handing out references, we also internalized its class to be sure). Now, what’s interesting is that from a consumer point of view, you don’t care whether the Item is implemented as a class or something else anymore. I think this is nice property in itself, because it hides implementation details, but besides that it actually frees up alternative implementations for the entire aggregate, meaning we’re not tied to a class per entity anymore (though we can still implement it this way, of course).
Now, would there be a reason why we’d want to do that? The answer is it depends. Like I said in the intro, the goal of DDD is to create a shared model of the domain and we need to be able to represent that model in code. Now, since we have multiple options of representing a concept in code, it becomes a matter of picking a representation that models the domain most clearly from code, and is therefore the more desirable model.
Does an object fit our model of a purchase item satisfactory? I think in this case, the purchase item is more like a data structure than an object: it has data, but doesn’t really have any behavior (since that has to go through the aggregate root). So no, looking at it from the (OO) perspective where an object should have behavior working on its private data, I don’t think an object matches the domain concept really well in this case. Of course, in practice, a data structure is usually represented as a class as well (unfortunately), which then does actually fit pretty well to the model.
An alternative implementation might use a simple Tuple, which would go as follows:
using PurchaseItem = Tuple<PurchaseItemId,PartNumber,int>; class Order { Dictionary<PurchaseItemId,PurchaseItem> _items = new Dictionary<PurchaseItemId,PurchaseItem>(); public void ChangeQuantity(PurchaseItemId itemId,int newQuantity) { if(!CheckOrderValueWithNewQuantity(itemId,newQuantity)) { throw new MaximumOrderValueExceeded(); } _items[itemId] = UpdateQuantityForItem(itemId,newQuantity); } ... }
This model conveys pretty clearly that there is no behavior associated with the PurchaseItems themselves, which is what we actually want to model. Also, you won’t be inclined to actually put any behavior on it, since you can’t.
In the end, working with tuples is probably gonna make the code a little less readable in C# (where are my record types?) so I might actually factor it out into a class, but in my opinion that’s a programming language driven compromise in model accuracy.
Conclusion
The primary reason I wrote this post is that I see the one-class-per-entity solution being applied in a lot of situations where it can actually cause harm, such as in the example where you could violate the aggregate’s invariant. In any other situation, we would think twice before exposing an object in that way to its consumers. But since we think this is the way that things are done, we end up having these kind of problems. We shouldn’t be doing that, and we should always stay critical to our own and other people’s solutions; they might be wrong or simply not apply to your context. I’d be happy to hear what others are thinking about this, so please use the comments.
BTW. It often also works the other way around: people trying to cram all the entity’s logic into 1 class. This is a way bigger problem, because it really clutters the aggregate’s code and you’re missing opportunities to abstract. Thus it perfectly valid to have classes in your domain layer that are not in your model. I’ll try to write about that later.