0

I was reading about DDD and I realize that sometimes an entity might be a VO or VO might be an entity. You can know which one is better depends on the context. I was checking different examples. For example, shopping cart DDD example. Product is an aggregate root, Cart is an aggregate root and items is a entity of Cart, so if you want to add a product to the cart you would do something like this:

$cart->addProduct(id $id, $name, $price)

class Cart
{
    private items;
    function addProduct(ProductId $id, ProductName $name, ProductPrice $price) {
        this->items[] = new Item(
            new ItemProductId($id->ToString()),
            new ItemName($name->ToString()),
            new ItemPrice($price->ToString()),
            new ItemCartId(this->id->ToString())
        );
    }
}

There are two reasons why I think it is a VO:

  1. You cannot modify the value's item ( only if the product's price has been modify there is a event that would modify its price).
  2. The item doesn't have id, it's has a reference of the product(ItemProductId) and a reference of the cart (ItemCartId)
jboot
  • 701
  • 9
  • 17
wipcrep
  • 19
  • 1
  • 7

2 Answers2

3

I was reading about DDD and I realize that sometimes an entity might be a VO or VO might be an entity. You can know which one is better depends on the context.

Usually its pretty clear whats entity and whats an value object. If it contains data that's fixed at the time of assignation, its a value object. For example "order address" on the order aggregate. When the order is placed, the address is set. "Addresses" may be an entity in user aggregate (i.e. a list of his common addresses), but for an order its an value object since its not supposed to change when the user edits or deletes one of his addresses.

cart->addProduct(id $id, $name, $price)

class Cart
{
    private items;
    function addProduct(ProductId $id, ProductName $name, ProductPrice $price) {
        this->items[] = new Item(
            new ItemProductId($id->ToString()),
            new ItemName($name->ToString()),
            new ItemPrice($price->ToString()),
            new ItemCartId(this->id->ToString())
        );
    }
}

That's a pretty bad example. Why would or should the value object be ItemPrice? Does that makes it any special? Why string? A price is usually just a numeric value but also involves a currency, passing it as string kinda beats that.

On top of that, having ItemCartId in the it does a) leak data persistence knowledge into your domain. The fact, it's contained inside this->items[] already establishes a relationship between the entity (or aggregate) and the value object. ItemCartId as no meaning in the domain, other than that it's required for relational database engines (=persistence knowledge)

There are two reasons why I think it is a VO:

You cannot modify the value's item ( only if the product's price has been modify there is a event that would modify its price).

You sure? Why would a eCommerce business want to have the prices in the card anyways?

Prices are informational only, they could change before the order is placed. Same as availability.

A lot of users put stuff in their cart and check on next day. In that time, the price could change.

No company would want to sell a product for the price when it was put into the shopping cart, if the price increased in the time since it was put in there. That would mean a financial loss.

Prices in the shopping carts are informational, not compulsory. You need know the exact process of the company.

The item doesn't have id, it's has a reference of the product(ItemProductId) and a reference of the cart (ItemCartId) Again. Why do you think ItemCartId belongs to the Item object? That's leaked persistence knowledge, since its only important for relational database systems.

All you really need in a shopping cart is * product or article number (not necessary the id, that's typically db knowledge) * quantity

Nothing else. If you may want to change the user when the price changed and show the old and new price, the take the price (=currency value object, not ItemPrice) to it too as a value to compare to an old state.

Finally and probably most importantly: Consider if the shopping cart is an aggregate at all (or does fit into ddd).

After all, most shopping carts are only a value bag w/o a lot of business logic into it. The real logic (checking the real price, product availability, asking for shipping location, calculation of taxes and shipping costs) happens during the checkout process, not while putting stuff into the cart.

For example you can check out eShops on Containers demo project showing an example shopping service based on microservices and ddd.

Some microservices apply DDD (such as Ordering microservice), where others don't (Catalog microservice or the Basket (cart) Microservice).

Applying DDD doesn't mean everything needs to be done with DDD. If its simple crud based services, then you don't need DDD for these. DDD adds a value where you have complex systems and complex business logic.

A catalog has neither, it just presents data which come from a different system (i.e. ERP which on other side may be built on using DDD).

Tseng
  • 61,549
  • 15
  • 193
  • 205
  • so let me get straight, to know if a object is a entity or VO, you have to think about if it can be modify. If the answer is not, then it's a VO but it's entity. Am I right? ""Addresses" may be an entity in user aggregate (i.e. a list of his common addresses)" "The fact, it's contained inside this->items[] already establishes a relationship between the entity (or aggregate) and the value object." I don't get it. So if you have a list of things it cannot be a VO? Why? – wipcrep Dec 12 '19 at 11:40
  • `$this->items[] = new Item(..)` already establishes the relationship. This item belongs to the card (since `items` is a cart property). This is the relationship, you **don't need** `CartId` on the item to describe this relationship, because the previous line already does it. Second, for the value object: – Tseng Dec 12 '19 at 11:58
  • A VO is not described by immutability only. A VO is described by that all of its value represent its identity. So for example, a price (=Currency value object) 5 EUR has two components. 5, the numeric value. They together represent the identity of this object. 4 EUR has a different identity than 5 EUR. Same for address. The whole of the address is its entity. Street, number, zip, city. If any of it changes, the identity changes (because its a different address). That's an value object by definition. An entity, doesn't do that, hence an identity needs an id field – Tseng Dec 12 '19 at 12:02
  • I understand about a VO has all the data as identity. If some value changes, the represent a differente identity. So if item has (product number and price) they will be an entity because if the price change, the item will be the same because of product name. But imagine that there is some bussinnes rule that says the price never changes. Despite of item has product number as its identity, item would be a VO because its price never change. Am I right? – wipcrep Dec 12 '19 at 14:44
  • What I said is that imho Item(position) should be just ArticleNumber and Quantity should be Value object. Price is not a concern of a shopping card. Its informational only and could be just fetched before displaying the shopping cart items in the WebUI, since they can change. All a shopping cart needs to know is what kind of product and how much of it. Availability and prices are only relevant for checkout (which by DDD means is a different bounded context) – Tseng Dec 14 '19 at 16:42
0

I don't understand what are you asking exactly, but the code you are providing could be improved.

First of all I suggest you to read the red book by Vaughn Vernon https://www.amazon.co.uk/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577: you can find 3 chapters describing how to define entities, value objects and aggregates, with some rules of thumbs.

One of those advices, is to keep your aggregates as small as possible, in order to improve your performance and keep the code easy to read and maintain. Imagine that you have a Blog aggregate that contains a list of Post entities: if you manage all of them in a single aggregate, when you want to modify the blog Author, for example, you are forced to retrieve all of the blog's post, without any reason, and that means that you are doing a join and slowing down your application. The more your aggregates grows, the slower those queries with their joins.

So, in the case of the Cart, I suggest you to build the cart without any item or product, instead you can add the CartId to the Item. Cart does not know which items it contains, but items know in which cart they are.

About value objects: is a tool that allows you to wrap some validation and business logic inside a class that is represented by its state and not by its id (like entities), so in the case of the cart, if you put two identical bottles of water inside it, how can you know that they are different? Do you need to know that they are different? Are they different if they are physically (or logically different) or are they different if some of their attribute is different?

In my opinion an item or a product, in your case, are entities because they are not measuring anything, and when you put an item twice, you actually have two different items (and you use an id to recognize them).

This is not necessary like this, sometime you can use a value object and sometimes an entity, it depends on your context. A good example to understand that difference is with money:

  1. if you want to measure an amount, for example 10 dollars, probably a value object will work for you, because you don't care if it a bill or another, you just want to measure 10 dollars; in this case if you have 10 dollars, is not important if you have a bill or another, the important thing is that is 10 and not 5

  2. in the case that you need to recognize different physical bills, for any reason (you need to track money for the police), you should use an entity, because any printed bill has a unique serial number, and a 10 dollar bill, in this context, is actually different from another 10 dollar bill

Hope this can help you.

Goodbye!

rastafermo
  • 408
  • 1
  • 5
  • 13
  • thanks! the reason why Cart has item is because when I create a cart some item is added. About item is an entity, I know every item is different because of its ids. But its id is a reference of product aggregate. So item doesn't have its own Identity. the items for a especific Cart will be different because one cart cannot have the same product. I Found this example: https://stackoverflow.com/questions/50455084/handling-aggregate-root where the guy said course in the BC2 is a valueobject and it has ID. Could you explain me? – wipcrep Dec 12 '19 at 09:06
  • _I suggest you to build the cart without any item or product, instead you can add the CartId to the Item._ How is that going to help? Sounds like leaking persistence knowledge into your domain. How you gonna ensure a product doesn't appear twice in the cart w/o cart being an aggregate? w/o knowing the exact case, Its not to be expected that a single cart will contain 1000 positions – Tseng Dec 12 '19 at 09:44
  • Hi, good comments, I'll try to answer everything. @wipcrep when you talk about identities inside a value object, you should clearly define that those VO are not identified by those IDs, but I still don't see how it could help you doing that, so I guess you have evaluated it. Tseng answer is quite clear about how to generally use tactical elements in DDD. – rastafermo Dec 15 '19 at 11:55
  • @Tseng I don't understand why you say that my solution leaks persistence into the domain, I'm just referencing another entity or aggregate by its ID, that's independent on how data is stored. Depending on the case, you can ensure aggregate invariants in different ways: the option I suggested will force you to keep a list of IDs inside you Cart if you want to validate duplicity of items. I'm just saying that is not strictly necessary to include a whole entity inside an aggregate to validate your domain, you could/should use only the info you really need. – rastafermo Dec 15 '19 at 11:58
  • Generally, I'm not saying that my solution IS the only one: is another point of view, another way to do it without breaking DDD rules, that allows you to improve performance and simplify your domain. Everyone should evaluate it's own business case and check if the solution fits. – rastafermo Dec 15 '19 at 12:02
  • Guess you don't really understand what aggregates are. Aggregates essentially are transaction boundaries. A cart position on its own can't be that, since when you check out or clear the card you (can) act on multiple items. If anything at all, the card is an aggregate and one car argue if position is entity or value object. Its not comparable with a blog and posts (though one would argue if simple blog does even comes with high enough complexity to justify ddd in the first place). Posts can stand on their own, cart items in a cart can't – Tseng Dec 15 '19 at 20:40
  • @wipcrep I repeat again my suggestion: think about alternative solutions depending on your business case and which limits do you have in your system. There is never only one solution. Never. And please, take your time to read and understand the answer of everyone, don't do like other that read and answer just to "win" a discussion or post a reply. – rastafermo Dec 17 '19 at 00:01