Ending the debate on value objects vs. entities: “Order lines” don’t exist

The seemingly endless debate around whether ordered items are value objects or entities needs to end:

Developers correctly point out that ordered items don’t have an identity. You don’t find, compare, or update them by ID but by the product they refer to. In other words, as a customer I don’t want to increase the quantity of line #5, I want to order another glass of Chardonnay. This discussion should simply end at this point and all horrible examples in articles and on github disappear. But somehow it never does. Instead you read this:

The aggregate must map the user’s commands to a particular line when it comes to editing an order on the screen, and it does it using the position of that line inside the order. The Position property provides the identity needed to make a decision which line to update or delete.

Vladimir Khorikov (2016)1

I politely but passionately disagree: This description is utterly removed from any real order use case. For my Chardonnay order, any POS can accomplish this either by simply adding another Chardonnay to my order, allowing duplicates, or by finding the Chardonnay position and increasing its quantity. Kindly note, that we always refer to the position by its product name, not it’s sequence number. The number is completely irrelevant when we decide which item we update.

If I changed my mind and cancelled my order of another glass of Chardonnay, which never happens but hypothetically assume this is an option in some kind of parallel universe: Cancelling an order would also happen through the product name. If the POS allowed duplicates, it would remove any of the added “lines” that match “Chardonnay” — it doesn’t matter whether that’s the Chardonnay at position #1 or #4. If it works with quantities, it would decrease the quantity on the item “Chardonnay”. Again, there is no need whatsoever to “identify” Chardonnay by its line number2.

Besides: Editing an order on the screen is a front-end concern. Yes, it’s practical to stack order items vertically (at least in Western languages). But merely because we don’t use columns like some Asian languages or find mind maps of the order not easy enough to comprehend, this doesn’t mean we need to process the order sequentially on the back-end. In fact, relying on the front-end and the sequence number it reports is dangerous. What if it messes up the order of lines for some kind of reason?

Aside from this, I also believe that this kind of thinking is a misconception and that it originates in three erroneous assumptions:

(1) Mistaking the UI for the domain model

(2) Mistaking billing for order concerns by not separating bounded contexts

(3) ORMs don’t like value objects (or at least they used to)

Mistaking the UI for the domain model

The quote above comes from a post that equates commercial orders with vending machines. It argues that people using the latter press a slot number to receive a specific product, hence we need to translate these numbers into items. The slot number is somewhat akin to a product ID we need to retrieve a specific product from an inventory.

But an order is not a vending machine and these are different use cases: When you order from an online shop, you don’t need to identify the right slot number or product ID for the item you want to order. You simply press the buy button for the right item that you have searched for by its name (and possibly verified by looking at a product photo). Actually, you would do the same, if the vending machine sported a nice, big touch display with icons or photos along the product names.

The language is wrong

One telltale sign for a wrong model in ddd is when ubiquitous language, the words and phrases used by your users, doesn’t know a term. Customers, whether that’s in online shopping or at the vending machine, don’t care about order lines or slots:

The doctor on a 24-hours shift standing in front of the coffee vending machine wants a double espresso, not item #2. This becomes painfully obvious when despite entering #2, she receives a Latte Macchiato instead. Perhaps the displayed list of coffees and their corresponding number is wrong, perhaps being very tired she made a mistake. In any case, she doesn’t have time for a 300ml drink, she just wants a quick caffeine boost.

The fact that your old-school number display doesn’t allow her to directly select “double espresso” by name or image, doesn’t turn position or slot into a feature of the domain model or even an identity. It turns an inventory concern into a customer concern and thus the customer into a kind of “human domain model”.

It also bakes a specific front-end, the number display, into the domain model, which creates an obstacle to ordering instead of a feature, as we witnessed above with the overworked doctor. If we changed the machine’s front-end and swapped the old number display for a fancy touch screen, any Order domain model based on slot numbers would lose its entire validity. Eric Evans (2003)3 calls this approach “Smart UI” and it’s rather the opposite of domain-driven design.

Mistaking invoicing for ordering concerns

Turning back to commercial orders, the concept of identification by position — or, local identity — becomes even more absurd. Mike Mogosanu is absolutely right in remarking that he’s never heard anyone say “please ship these order lines”. Where we customarily do find lines is the paper trail every order creates: The shipping note and the invoice or receipt, which are printed as some kind of list (again, in the Western world). I imagine that developers all over the world visualise these when thinking about orders; hence “order lines” or “order line items”

But shipping notes and invoices aren’t the same as orders. In fact, shipping notes might not contain all items ordered and invoices might add extra lines. That’s why rendering notes and receipts is an infrastructure concern and should be completely decoupled from a domain model.

One might want invoice lines to be sequenced a specific way for reasons of comprehensibility. For example, we might always want to list a delivered product like a washing machine and its associated, booked service “installation” right after.

There might even be a business rule for delivery (but not order!) that washing machines can only be shipped if an appointment for installation with a friendly plumber has also been scheduled. But this kind of coordination or sequencing is entirely irrelevant to order that only states desires and costs.

Order item sequence is irrelevant

In fact, I can’t think of any business rule that applies to the sequencing of order items. Amazon doesn’t care in what order you add a pullover, a book, a camera, an mp3 music album, and a new fridge to your order. The supermarket cashier doesn’t bother what items you put first or last on the belt. You’re entirely making your own life hell if you put the fragile and light items first. All that matters is that nothing drops off the belt and that you pay for everything.

There might be contexts in which delivery of certain items is time-sensitive. For example, at the restaurant we want to receive the starters first and the dishes of the main course later. Does this mean we need to sequence these items in our order?

Not at all: If you work with restaurants, you will discover that there’s a concept called “course” comprising dishes a table party wants to receive together. “Course” would likely be a property of an order item and order items might even change their state from requested to “fired”, which is restaurant jargon for notifying the kitchen that they should start prep. Merely assigning a “line number” would not tell the waiting staff which items belong to a course.

By the way, just because its constructor would take a “course” property, that doesn’t turn an order item into an entity either. On the contrary: Despite referring to exactly the same product, the salad ordered as a starter is now materially different from the one ordered as a main course by a guest on a diet who hasn’t discovered Ozempic yet. If we asked, if the first equals the second, the answer would be “no, it’s not” and the reason for this isn’t the starter being written as line 5 and the main course as line 11 but that they belong to different courses.

ORMs used not to like value objects

Veering off from domain modelling to infrastructure another factor swaying people towards using entities over value objects comes in sight: the ORM. Their early versions simply didn’t provide any option to save value object collections such as order items. In fact, Doctrine doesn’t provide one to this day.

Conscientious engineers proposed solutions such as nesting value objects inside wrapper entities, so you could swiftly replace them. But that rendered iterating through the collection, for example to calculate the total order amount, rather tedious. At the end of the day many developers simply resorted to persisting value object collections like one-to-many associations (without internal value objects) accepting all the costs this approach entails. If you had to assign IDs anyway, you would simply create a combined identifier consisting of the order ID and the “line number”.

Fortunately, this is 2025 and we now have niftier solutions like NoSQL databases or a hybrid models using JSON fields with traditional SQL databases. Saving value object collection becomes a breeze if you can simply serialise them for persistence and still query them almost like ordinary columns. It’s also rather fast especially since orders usually don’t include hundreds of thousands of items.

While Doctrine generally supports persisting to JSON fields, neither it nor Symfony come with an out-of-the box dbal type. For this, simply pop in Kevin Dunglas’s wonderful JSON ODM plug-in along with the Scienta JSON query functions. (Kindly note that you have to register these in doctrine.yaml first. )

Order items are value objects

In this article how doubts about order items being value objects are created by ignoring the ubiquitous language that never uses “order lines” and making two fundamental mistakes: Mistaking a (possibly outdated) UI for the domain model or mixing up invoice and order concerns. I showed that there really is no conceivable use case for sequencing order items and neither one in which they need a “local identity”. This would ideally close the case and prompt everyone to remove their useless “position” properties from their order item value objects… but I guess poor examples using “order lines” or “order line items” will stay with us for quite a bit until all ORMs properly support JSON fields out of the box.

Khorikov, V. (2016). Classes internal to an aggregate: entities or value objects? Enterprise Craftsmanship
By the way, how do you deal with removals if you assign positions to order items? By replacing all items after the one you removed with a decreased position? Sounds awful to me.

Would it be a bad idea to get in touch?

If you have a project in mind, need wise advice, or just want to say hi: Write on to { click/tap to reveal }

© 2026. Lazy Developer Studio. All rights reserved.