Classes internal to an aggregate: entities or value objects?

By Vladimir Khorikov

While such classes as Person and Money are pretty intuitive and can be easily attributed to either entities or value objects, the choice isn’t so obvious when it comes to classes that are internal to an aggregate. That is classes that aren’t roots of their own aggregates but rather parts of existing ones.

Internal classes: examples

This post is an answer to an interesting question I was asked on my Pluralsight course‘s discussion board.

To give you some context, the sample project I used in the course has roughly the following object model:

public class SnackMachine

{

    private List<Slot> Slots { get; set; }

}

public class Slot

{

    public int Quantity { get; set; }

    public Snack Snack { get; private set; }

    public decimal Price { get; private set; }

    public int Position { get; private set; }

}

Each snack machine contains a fixed set of slots, and each slot has a number of snacks which are being sold at some price. When a user buys a snack, Quantity is decreased by one, that’s why it has a public setter; other properties stay unchanged. The Slot class is a part of the Snack Machine aggregate.

The question goes as this (paraphrased): “Should we treat Slot as an Entity or as a Value Object?”

The issue with the code above is that while the snack machine class is clearly an entity, it is not so obvious where to attribute the Slot class. On one hand, it is mutable as we can change the number of snacks left, so it should be an entity.

On the other, do we really care about its identity? As I wrote previously, the way we compare objects to each other is essential. Each snack machine is unique in a sense that we don’t treat them interchangeably even if they contain the same set of slots. Slots themselves, however, aren’t unique. If a machine has two slots with the same sets of properties, we can very well replace one by another. Doesn’t it mean Slot is a value object?

This dichotomy is a common issue with classes that are internal to an aggregate. People (me included) often struggle about whether they should treat them as entities or value objects. Here’s an example with an Order class (taken from this article):

public class Order

{

    private List<OrderLine> Lines { get; set; }

}

 

public class OrderLine

{

    public int Position { get; set; }

    public bool IsCanceled { get; set; }

    public int Quantity { get; private set; }

    public Guid ProductId { get; private set; }

    public decimal Price { get; private set; }

}

There’s essentially the same problem here. While we do need to distinguish orders regardless of their content, we don’t really care that much about order lines. Two lines of the same product, quantity and price are indiscernible for the end user. We can replace one with another of the same properties and nobody will notice.

So, how to resolve this problem? Are such classes really value objects? Or are they entities?

The concept of Local Identity

While it’s true that instances of the Slot and OrderLine classes are indistinguishable to an external user, they still possess an identity of a different kind: Local Identity.

The Slot class does have an individuality but that individuality is meaningful only within the boundaries of its aggregate. A snack machine needs to distinguish its slots somehow in order to decide which of them it has to work with in a given transaction. It does it by locating a slot by its position. The Position property essentially acts as a local identity here.

The same is applicable to the OrderLine class. 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.

This makes both Slot and OrderLine classes entities. But there’s more in that.

If we look at the use cases for the Slot entity, we will notice that the fields Snack, Price, and Quantity are always used together. The same is true for OrderLine: whenever we need to display a line, not only do we show what the product that is, but we also indicate its price and the quantity the user ordered.

This makes these properties a cohesive whole inside their entities. It means that we can define a new concept for them. Here is an example for the Slot class:

public class SnackMachine : AggregateRoot

{

    private List<Slot> Slots { get; set; }

       

    public void BuySnack(int position)

    {

        Slot slot = GetSlot(position);

        slot.SnackPile = slot.SnackPile.SubtractOne();

    }

}

public class Slot : Entity

{

    public SnackPile SnackPile { get; set; }

    public int Position { get; }

}

public class SnackPile : ValueObject<SnackPile>

{

    public Snack Snack { get; private set; }

    public int Quantity { get; private set; }

    public decimal Price { get; private set; }

 

    public SnackPile SubtractOne()

    {

        return new SnackPile(Snack, Quantity – 1, Price);

    }

}

Note that the new concept – SnackPile – is a value object. It’s immutable (we don’t mutate the existing instances but rather create new ones using the SubtractOne method) and it can be treated interchangeably. The same technique is applicable to the OrderLine entity: we can extract a ProductPile value object out of it.

This is an example of the guideline I wrote about previously: we should always try to move as much logic from entities to value objects as possible. The Slot entity here acts as a thin wrapper on top of the value object. The only thing it does is carries an identity – the Position property. All the actual work is delegated to the SnackPile class. It’s a quite powerful technique as it allows us to significantly simplify the domain model.

Summary

It’s not always clear how to categorize classes that are internal to an aggregate. It might be tempting to attribute them to value objects as they don’t have an obvious identity that is seen from the outside world. However, they do possess a special kind of it – Local Identity. That is an identity which is meaningful only within the aggregate’s boundaries.

Source code

Source code for the Snack Machine and Slot classes

Related articles:





  • Jacob Zimmerman

    Good stuff.
    This is another classic case of a problem being solved by adding another layer of abstraction 🙂

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Thanks! 🙂

  • Dimitris Foukas

    Hate to spoil the consensus but using position as an entity Id is a violation of the Rel DB principle that Ids should not have an intrinsic meaning! You could see that more readily if your chosen Id types were not integers but (say) Guids… In Legacy Oracle systems you can still see Order Lines that have composite keys – one its parent aggregate and the other a unq sequence, AFAIK Oracle has some way to generate unique sequences for exactly this reason (I am showing my age here!)

    I really enjoyed your PS class but IMO slots are really Value Objects just like Order Lines. It is an artifact of the inefficiencies of our current ORM technology that we have treat collections of Value Object as entities. If were to use a document store we could place them in a single document along with their Aggregate root object – the current issue with (most of them) is with querying across documents! Perhaps we should bypass ORM technology altogether…

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      There’s no consensus on this topic, so you are not spoiling anything 🙂

      Rel DB principle that Ids should not have an intrinsic meaning!

      That’s correct, but I wasn’t writing about RDB Ids in the article. I was talking about inherent identity of the Slot and OrderLine concepts in the problem domain. There’s a substantial difference here: RDB Ids is a physical mechanism of how we identify rows in the database, whereas an intrinsic identity is how the entities are identified in our domain model. On the database level, we still assign surrogate keys to them but those keys don’t quite represent their actual semantic identity (which is fine for the most part).

      Overall, composite Ids are indeed a mess and I wouldn’t recommend anyone to use them.

      If were to use a document store we could place them in a single document along with their Aggregate root object

      That’s exactly how we should do it with a document store, but that doesn’t mean order lines would be value objects here. A document in a NoSQL DB is a whole aggregate, not a single entity, it can very well contain other entities as well.

  • Michael G.

    What are your thoughts on storing persisted Value Objects as 1-to-1 relations in the database, rather than inline their values, when those values can be non-existent (null)?

    Example:
    A Payment can have a Refund.

    public class Payment
    {
    public decimal Amount { get; }
    public DateTime PaymentTime { get; }
    public Maybe Refund { get; }
    }

    public class Refund : ValueObject
    {
    public decimal Amount { get; }
    public DateTime RefundTime { get; }
    public string Reason { get; }
    }

    Inlined version:
    [Payment]
    PaymentId, Amount, PaymentTime, RefundAmount, RefundTime, RefundReason

    1-to-1 relation version:
    [Payment]
    PaymentId, Amount, PaymentTime

    [Refund]
    PaymentId, Amount, RefundTime, Reason

    For the inlined version, several null values are introduced, and the Payment table will need to be refactored when/if there is a change to the Refund data.

    For the 1-to-1 relation version, changes to the Refund data will not impact a Payment at all, and there are no null values introduced at all.

    Is this a good practice, or should Value Objects always be inlined?

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      1-to-1 relationships are good in that they don’t allow value objects to exist without a parent entity (FK constraint from Refund.PaymentId to Payment.PaymentId is needed) but there still are issues with it.

      The first one – you still have an Id field in the Refund tale and you will have to deal with it somehow in your domain model. At the same time, Value Objects shouldn’t have an identity. The second one – 1-to-1 relations are hard to manage from the ORM perspective.

      The only benefit 1-to-1 relationships introduce – the lack of nulls on the DB level – doesn’t justify having these issues. I don’t think the other benefit you mentioned is really a benefit. It doesn’t matter which table you need to modify should a change to the Value Object happen.

      So, I would still inline the Value Object into the parent entity’s table.

      • Michael G.

        The ID on the Value Object isn’t required though. It is merely an artifact of the relational database in which it is stored. NHibernate is fully capable of handling a 1-to-1 relation through th HasOne() mapping, without ever needing to include the ID property on the Value Object. As long as the Entity is mapped to be responsible for saving changes to the Value Object, it should be fine.

        • http://enterprisecraftsmanship.com/ Vladimir Khorikov

          Interesting, didn’t know about that. In this case, it’s more of a personnel preference then. I would still prefer a single table implementation, just because it’s easier to deal with a single DB table.