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
newOrderThe 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?
ChatGPT 4o response
This is an excellent question, and it gets to the heart of domain modeling: deciding whether a concept should be modeled as an Entity or a Value Object. Let’s examine whether
OrderLineshould really be treated as an entity in the example.Recap: Entity vs. Value Object
The key question to ask:
Is
OrderLinean Entity?In the book,
OrderLineis modeled as an entity, but whether this makes sense depends on your domain:Arguments for
OrderLineas an Entity:OrderLinehas a lifecycle of its own (e.g., it can be modified, tracked, or deleted independently of theOrder), it’s a strong candidate for an entity. For example:OrderLine(e.g., when the quantity was changed, by whom, etc.).OrderLinehas its own business processes (e.g., workflows like inventory reservations or backorders).OrderLineId(e.g., for auditing or integration with other systems), the line’s identity becomes important. In this case,OrderLinebeing an entity makes sense.OrderLineswith the sameProductCode,Quantity, andPriceare still treated as distinct (e.g., they represent separate transactions), thenOrderLineneeds an identity beyond its attributes.Arguments for
OrderLineas a Value Object:OrderLineas entirely defined by its attributes (ProductCode,OrderQuantity,Price, etc.), there’s no need for a unique identity. Two OrderLines with the same values are indistinguishable, making them Value Objects.OrderLineis immutable, any change (e.g., toOrderQuantity) would result in replacing the oldOrderLinewith a new one. The identity is irrelevant because changes propagate through theOrderaggregate.OrderLineexists purely as part of theOrderaggregate and has no meaningful existence outside of it, modeling it as a Value Object simplifies the design.OrderLineas a Value Object removes the need to manage an additional ID (OrderLineId). Instead, its uniqueness within theOrdercan be enforced by a combination ofProductCodeand possiblyOrderId.When does
OrderLinework better as a Value Object?Example Scenario
Imagine an e-commerce system where:
Orderis essentially a collection ofOrderLines, and theOrderLineis just a way to represent a product (ProductCode) and its associated quantity and price.OrderLineindependently (e.g., it doesn’t need to log specific changes toOrderLineattributes or treat them as distinct entities).OrderLinealways flow through theOrderaggregate.In this case,
OrderLinecan be modeled as:The Order would then manage
OrderLineas a collection of Value Objects:When updating an
OrderLine(e.g., changing its price), you simply replace the oldOrderLinewith a new one, maintaining immutability:Trade-offs of Treating OrderLine as a Value Object
OrderLineId, which reduces complexity and keeps the aggregate focused.OrderLinenaturally propagate through the aggregate, which aligns well with the immutable data structure principles in functional programming.OrderLine’s identity, treating it as a Value Object better matches the ubiquitous language.When does OrderLine need to be an Entity?
There are cases where
OrderLineshould retain its identity:OrderLineover time (e.g., who changed the quantity or price, and when), thenOrderLineIdbecomes necessary to distinguish between instances.OrderLineId(e.g., references in external systems), you need to preserve its identity.OrderLineis involved in complex workflows, like inventory management or shipping logistics, treating it as an entity can make the model more expressive.So, whether
OrderLineshould be an Entity or a Value Object depends entirely on the domain requirements and how the ubiquitous language describes it. If the identity of theOrderLinematters in your domain, stick with it as an entity. But if theOrderLineis just a compositional part of theOrderand defined solely by its attributes, modeling it as a Value Object simplifies the design and aligns better with immutability principles.