2

Question is - can Entity be defined by all of it's properties or only by it's Id.

This is the example:

class Wallet{
    int id;

    Map<BillType, Bill> bills;  //can have only 1 bill per bill type

    void addBill(BillType billType, Bill bill){
        this.bills.put(billType, bill);
    }
}

//this is probably an Entity, since it is mutable, but it has no global Id (only local bound to wallet)
//and equality is based on all of the properties
class Bill{
    BillType billType;
    Map<TypeOfBillSide, SideOfBill> billSides;  //can have only front or back

    Bill(BillType billType){
        this.billType = billType;
    }

    void drawWithPenOnBillSide(TypeOfBillSide typeOfBillSide, String drawing){
        this.billSides.get(typeOfBillSide).drawWithPenOnBillSide(drawing);
    }

    void burn(){
        System.out.println("I burned this bill");
    }
}

//this is probably an Entity, since it is mutable, but it has no global Id (only local bound to Bill)
//and equality is based on all of the properties
class SideOfBill{
    TypeOfBillSide typeOfBillSide;
    String drawing;

    public SideOfBill(TypeOfBillSide typeOfBillSide) {
        this.typeOfBillSide = typeOfBillSide;
    }

    void drawWithPenOnBillSide(String drawing){
        this.drawing = drawing;
        System.out.println("I draw on this side " + this.drawing);
    }
}

enum BillType{
    DOLLAR_10,
    DOLLAR_20,
    DOLLAR_50;
}

enum TypeOfBillSide{
    FRONT_SIDE,
    BACK_SIDE
}

Here I have globally unique Wallet - that is Aggregate Root. It has Bills which I think are entities in this case (since I can alter the state of the Bill and it is still that bill that is in the wallet). State can be altered by drawing some string on any of the sides of the bill (SideOfBill - which is also an entity in this case).

Bill by itself has meaning only as part of wallet, and also in wallet I can have only 1 kind of bill (I cannot have 2 bills with 10$ eg. only one).

If I treat this as Value object, and make it immutable, then each time I draw something on the Bill, I have to make new bill - which in this case is a bit strange and also hard to do in code.

If I treat this as the entity that is globally unique, I would have to have id for Bill that is actually composite from [Wallet.Id & Bill.billType]. But Wallet.id does not fits naturally to Bill class in this case.

The most natural thing is that I treat Bill as Entity and have equals method that tests all of the Bill properties (at the same time all properties of the SideOfBill since it is contained in Bill class).

Is this common case to have?

Bojan Vukasovic
  • 2,054
  • 22
  • 43
  • If It's an entity then why not just add an ID ? – Ofir Winegarten Feb 20 '17 at 14:02
  • @OfirWinegarten because I don't need the Id. That's the thing - I don't know for sure if I should model this as entity or value object. I need to have option to change the state (draw on the bill), but at the same time two bills that are not in the wallet and have same drawing can be considered equal. – Bojan Vukasovic Feb 20 '17 at 14:04
  • Then i believe it should be a value-object, since you don't care about instances, but about the content. – Ofir Winegarten Feb 20 '17 at 14:11
  • And `SideOfBill` as well – Ofir Winegarten Feb 20 '17 at 14:12
  • @OfirWinegarten it can be that way. The problem with this approach is that if I need to draw something on the side - I have to create new SideOfTheBill each time, and then create new Bill with this new updated SideOfTheBill (since Value Objects should be immutable) - seems like lot of code? – Bojan Vukasovic Feb 20 '17 at 14:50
  • That sounds right. It doesn't sound like too much code - these are very simple objects. – Ofir Winegarten Feb 20 '17 at 15:29
  • @OfirWinegarten in the example yes. But suppose that it has so many more methods and sub-children. That was the problem for me - seems like overkill to have to clone all the things and make new objects each time. – Bojan Vukasovic Feb 20 '17 at 15:57
  • @bojan55 Although not preferable, value objects can be mutable but, you just need to make sure you dont share them in that case. Also, do not forget that the ID of an entity just have to be unique within it's AR. In other words, `billType` seems enough to serve as an ID (from the domain model perspective). For the DB you may use both `walletId` and `billType`. – plalx Feb 20 '17 at 18:20

3 Answers3

2

Although it is not common practice, Value Objects (VO) surely can be mutables (e.g. for performance reasons). However, you need to make sure that mutable VOs aren't shared.

Still, the need for a mutable VO is perhaps a strong indicator that the concept you are trying to model is in fact an entity. A good question to ask yourself is whether or not you are interested about the lifecycle of this instance.

For example, in your case would it be important to keep the history of changes that were made on a bill? If it is then a bill should be modeled as an entity.

If I treat this as the entity that is globally unique, I would have to have id for Bill that is actually composite from [Wallet.Id & Bill.billType]. But Wallet.id does not fits naturally to Bill class in this case.

Do not forget that from the domain model perspective, entities must only be uniquely identified within their Aggregate Root (AR). That means billType could serve as a bill's ID within a wallet.

Also note that the bill could have a surrogate identity from the database perspective or a (walletId, billType) composite ID if needed.

plalx
  • 42,889
  • 6
  • 74
  • 90
  • In this case, Bill is unique inside the wallet (you cannot have 2 bills with same BillType). I'm interested in life-cycle of a Bill - meaning I care about drawings that are drawn on any of the sides of the bill. I would make this Entity, but I just cannot imagine equals method with only BillType as id. That means that if I compare the Bill inside a Wallet and one Bill that I create manually with the same BillType (but with different drawing) it will be the same, which does not make sense. – Bojan Vukasovic Feb 21 '17 at 09:17
  • @bojanv55 That does make sense. The state of the entity is irrelevant. It would be the same entity in two different states. – plalx Feb 21 '17 at 14:28
  • OK. Seems logical somewhat :) – Bojan Vukasovic Feb 21 '17 at 14:33
  • @bojanv55 It's the same with all entities. I could create 10 instances of any aggregate root with the same ID in memory. Your Unit of Work should be able to deal with this (e.g. by using an identity map). If you try to add two different instances with the same ID it would throw an exception saying the ID must be unique or something like that. – plalx Feb 21 '17 at 14:40
0

Couple of notes:

  1. It's important to make a good decision which objects are entities and which are VO. VO identity is determined by it's content (the value it represents).
    It's not so clear from the case you are describing.
    Generally, 10$ are 10$ and Money is the first example that all books show when talking about VO.
    But, a bill might be different and might be considered entity. I can have a 10$ bill and you can have one. Their value is the same (they are equal in that term), but they are still two different entities and therefore should have some identity field. However, This might not be your case.
    If you do consider the bills to be the same, if they have the same billType and drawing then it's a VO.

  2. For the second part of the disscussion, VO, usually, are immutable by nature and definition. If you have a complex VO then you can consider using the famous Builder pattern.

Ofir Winegarten
  • 9,215
  • 2
  • 21
  • 27
0

The general idea is to use the next rules:

1) DDD is all about business and relations between concepts/notions. The implementation is not strictly defined. Thus it is better to write detailed story and then show how you see it on the Domain Map.

2) An entity is:

Many objects are not fundamentally defined by their attributes, but rather by a thread of continuity and identity. (Evans)

For example, an order can have OrderItems, Price, ShippingAddress, and other attributes. If somebody changes ShippingAddress the order stays the same object identified by its OrderNumber, it is not a new order.

Even if you have two orders in the system with all the same attributes (OrderItems, Price, ShippingAddress) they are still different entities. The only difference is the identity: OrderNumber.

3) Value object is defined by all of its attributes. So it is common and convenient to do it as immutable.

The obvious example is Price:

Price{
  readonly Currency Currency;
  readonly Decimal Amount;
}
value1 = new Price(Currency.USD, 1);
value2 = new Price(Currency.USD, 1);

Assert.IsTrue(value1 == value2);

Less obvious example is a usage of VO in Aggregate Roots, which seems to apply to your example.

Order has OrderItems, where

OrderItem{
  string ProductSKU;
  int Amount;
}

It might be convenient to make OrderItem as Entity and add OrderItemId attribute, for example, for database editing. From a business perspective most of the time there is no idea of OrderItemId at all. Order items live inside their Aggregate Root Order and strictly identified outside as a pair {Order, OrderItem}. In such case you even cannot touch OrderItem without going at first to its Aggregate Root.

Now if we look at OrderItem it is absolutely identified by its attributes, then it is the Value Object.


So, "can Entity be identified by all of its properties?" - No, it is the notion of a Value Object.

Artur A
  • 7,115
  • 57
  • 60