In chapter 5, p.95, Scott writes:
"First, is Order an Entity or a Value Object? Obviously it’s an Entity—the details of the order may change over time, but it’s the same order.
What about an OrderLine, though? If we change the quantity of a particular order line, for example, is it still the same order line? In most designs, it would make sense to say yes, it is still the same order line, even though the quantity or price has changed over time. So OrderLine is an Entity too, with its own identifier.
But now here’s a question: if you change an order line, have you also changed the order that it belongs to?
In this case, it’s clear that the answer is yes: changing a line also changes the entire order. In fact, having immutable data structures makes this unavoidable. If I have an immutable Order containing immutable OrderLines, then just making a copy of one of the order lines does not also make a copy of the Order as well. In order to make a change to an OrderLine contained in an Order, I need to make the change at the level of the Order, not at the level of the OrderLine.
For example, here’s some pseudocode for updating the price of an order line:
/// We pass in three parameters:
/// * the top-level order
/// * the id of the order line we want to change
/// * the new price
let changeOrderLinePrice order orderLineId newPrice =
// 1. find the line to change using the orderLineId
let orderLine = order.OrderLines |> findOrderLine orderLineId // 2. make a new version of the OrderLine with the new price
let newOrderLine = {orderLine with Price = newPrice}
// 3. create a new list of lines, replacing // the old line with the new line
let newOrderLines =
order.OrderLines |> replaceOrderLine orderLineId newOrderLine
// 4. make a new version of the entire order, replacing // all the old lines with the new lines
let newOrder = {order with OrderLines = newOrderLines}
// 5. return the new order
newOrder
The final result, the output of the function, is a new Order containing a new list of lines, where one of the lines has a new price. You can see that immutability causes a ripple effect in a data structure, whereby changing one low-level component can force changes to higher-level components too."
where Order and OrderLine are defined in the book like this:
type Order = {
Id : OrderId // id for entity
CustomerId : CustomerId // customer reference
ShippingAddress : ShippingAddress
BillingAddress : BillingAddress
OrderLines : OrderLine list
AmountToBill : BillingAmount
}
and OrderLine = {
Id : OrderLineId // id for entity
OrderId : OrderId
ProductCode : ProductCode
OrderQuantity : OrderQuantity
Price : Price
}
I'm not fully convinced of having the Order
aggregate having a changeOrderLinePrice
function that takes an orderLineId
as one of its parameters... isn't this forcing the clients using this function to know what the id of the order line is in order to change the price?
Also, are we sure that OrderLine
should be modelled as an Entity? Can we consider it instead a ValueObject composed by a ProductCode
, OrderId
, Quantity
and Price
?
DeepSeek R1 response
The decision to model
OrderLine
as an Entity or Value Object hinges on the domain requirements and the lifecycle of theOrderLine
. Let’s break down the trade-offs:Option 1:
OrderLine
as a Value ObjectIf
OrderLine
is a Value Object, it has no identity and is defined solely by its attributes:ProductCode
,OrderId
,Quantity
, andPrice
. Any change to these attributes creates a newOrderLine
.Pros:
OrderLineId
or track identity.OrderLine
is merely a collection of attributes (e.g., in a read-only order).Cons:
ProductCode
andPrice
), they are indistinguishable.ProductCode
), which may not be reliable or practical.Example:
Option 2:
OrderLine
as an EntityIf
OrderLine
is an Entity, it has a uniqueOrderLineId
that persists even when its attributes change. This is the approach in the book.Pros:
OrderLineId
, avoiding ambiguity.Cons:
OrderLineId
and ensuring it’s propagated correctly.Example:
When to Choose Which Model?
OrderLineId
for precision.OrderLineId
.Recommendation
The book models
OrderLine
as an Entity because:changeOrderLinePrice
function, implying lines can be updated.OrderLineId
avoids ambiguity when multiple lines have the sameProductCode
.However, if your domain treats
OrderLine
as immutable (e.g., once an order is placed, lines cannot change), a Value Object could work. For example, in a system where orders are read-only snapshots after creation, you might omitOrderLineId
.Final Note
The choice depends on how the domain treats
OrderLine
:The book’s design is appropriate for a domain where lines can evolve, but your critique highlights a valid consideration: always validate the model against real-world requirements.