Nesting a Value Object inside an Entity

In this post, we are going to look at what to do if you are not able to treat some concept in your domain as a Value Object and have to make it an Entity. TL;DR: create a nested Value Object inside that Entity and relocate as much domain logic to that Value Object as possible.

Nesting a Value Object inside an Entity

In the last post, we discussed how to represent a collection of Value Objects as a Value Object itself. The solution is to make it obey the same rules as other Value Objects: define structural equality, make sure it is immutable and serialize the whole collection into a string when persisting it to a data store.

However, because of the last serialization part, that solution is sub-optimal if you need to perform a search among those Value Objects using standard database features. It is also not the best implementation if the number of items in the collection is too high as it might cause performance issues.

In this case, you don’t have any choice other than persisting such value objects in a separate table. This entails the necessity to deal with the identifiers assigned to them on the database level, and that essentially means treating items in the collection as Entities.

Let’s take the example from the previous post. Here’s the City value object:

public sealed class City : ValueObject<City>
{
    public string Name { get; }
    public bool IsEnabled { get; }
 
    public City(string name, bool isEnabled)
    {
        Name = name;
        IsEnabled = isEnabled;
    }
 
    protected override bool EqualsCore(City other)
    {
        return Name == other.Name && IsEnabled == other.IsEnabled;
    }
 
    protected override int GetHashCodeCore()
    {
        return Name.GetHashCode() ^ IsEnabled.GetHashCode();
    }
}

There also is a User entity which holds a collection of cities. Let’s say that each user might have hundreds of those cities and it is not practical - due to performance reasons - to serialize them into a string every time you persist a user. In this case, you have to create a separate table for City:

City entity
City entity

And, as a result, you need to convert the City class from a Value Object into an Entity:

public class City : Entity
{
    // Id is in the Entity base class
    public string Name { get; protected set; }
    public bool IsEnabled { get; protected set; }
    public User User { get; protected set; }
 
    public City(string name, bool isEnabled, User user)
    {
        Name = name;
        IsEnabled = isEnabled;
        User = user;
    }
}

This solves the problem with performance, but now we’ve complicated the model. Instead of just a single entity - User entity - we now have two of them. And the City entity clearly has a Value Object semantics: we don’t care about its identity and we don’t mind to replace one with another as long as their names match.

Is there a way to mitigate that issue?

Luckily, yes. There is a compromise solution. You can extract a value object out of City and transfer all business logic the entity possesses to it. The entity, in this case, will act as a thin wrapper on top of that value object with just an identifier and a reference to the corresponding user. The resulting value object, at the same time, will have the same structure as before:

internal class CityInUser : Entity
{
    public City City { get; protected set; }
    public User User { get; protected set; }
 
    public CityInUser(City city, User user)
    {
        City = city;
        User = user;
    }
}
 
public sealed class City : ValueObject<City>
{
    public string Name { get; }
    public bool IsEnabled { get; }
 
    /* ... */
}

Note that I renamed the entity to CityInUser so that there’s no collision in names between the two classes. Also note that I made it internal. The best part in this approach is that although you do have an additional entity in your domain model, you don’t ever have to expose it outside of the aggregate. The client code that works with User doesn’t have to be aware of that auxiliary entity and can work with the City value object directly.

For example, here’s how User might expose the collection of cities and addition of new items to it:

public class User : Entity
{
    private IList<CityInUser> _cities;
 
    public IReadOnlyList<City> Cities =>
        _cities
            .Select(x => x.City)
            .ToList();
 
    public void AddCity(City city)
    {
        _cities.Add(new CityInUser(city, this));
    }
}

As you can see, both methods expose value objects and perform the necessary conversions internally making the life of the client code easier.

Nesting a Value Object inside an Entity: examples

It’s important to keep the wrapping entity as shallow as possible. That will help you adhere to the "Value Objects over Entities" guideline, and will provide a nice separation of concerns: the entity, in this case, will be responsible for "technical stuff", such as holding an identity imposed by the database, while the value object will contain the domain logic.

Let’s take some more examples of where this approach is applicable.

Some time ago, I needed to create an Organization class and assign a collection of emails to it. Treating that collection as a value object wouldn’t fit the requirements because I had to ensure the emails' uniqueness on the database level. So the only way to proceed with the task was to introduce a separate table for organizations' emails:

Email value object
Email value object

Emails themselves here are clearly value objects. Fortunately, we can still treat them as such if we apply the approach I described earlier:

public class Organization : Entity
{
    private IList<OrganizationEmail> _emails;
 
    public IReadOnlyList<Email> Emails =>
        _emails
            .Select(x => x.Email)
            .ToList();
 
    public void AddEmail(Email email)
    {
        _emails.Add(new OrganizationEmail(email, this));
    }
}

All business logic can be placed into Email the value object, thus providing all the benefits of dealing with value objects as opposed to entities.

Another example is the well-known OrderLine sample. This problem doesn’t quite fit the narrative I’m following here but has a similar solution. So, here’s the code:

public class Order
{
    private IList<OrderLine> Lines { get; set; }
}
 
public class OrderLine
{
    public int Position { get; set; }
    public Order Order { get; private set; }
    public int Quantity { get; private set; }
    public Product Product { get; private set; }
    public decimal Price { get; private set; }
}

The Order class it clearly an entity. Moreover, it’s an aggregate root. As for OrderLine, it might seem uncertain what it is. On one hand, we want to treat lines in an order interchangeably but on the other, we still want to mutate their position - order lines are not entirely immutable here.

This dichotomy is false, however, because what we have is two concepts that include one another. The OrderLine class itself is an entity: it’s mutable and has an identity local to its aggregate which is defined by its Position property. At the same time, the three fields in it - Quantity, Product, and Price - do comprise a value object, a conceptual whole which doesn’t possess an identity of any kind and which can be deemed replaceable.

The solution to this problem is the same as to the previous one: implement OrderLine as an entity and introduce a nested value object.

public class Order : Entity
{
    private IList<LineInOrder> _lines;
 
    public IReadOnlyList<OrderLine> Lines =>
        _lines
            .OrderBy(x => x.Position)
            .Select(x => x.Line)
            .ToList();
}
 
internal class LineInOrder : Entity
{
    public int Position { get; set; }
    public Order Order { get; private set; }
    public OrderLine Line { get; private set; }
}
 
public sealed class OrderLine : ValueObject<OrderLine>
{
    public int Quantity { get; }
    public Product Product { get; }
    public decimal Price { get; }
}

Once again, we are able to hide the entity by making it internal and exposing only the value object to the outside world. And that value object is capable of keeping all business logic that belongs to a line. The only difficulty here is that we have to come up with a made-up name for that entity to avoid collision with the value object.

Here’s a fully-fledged example of an analogous problem from my DDD in Practice Pluralsight course: Slot entity and SnackPile value object.

Summary

Nesting a value object into an entity is a powerful technique that can help you bring your code closer to the "Value Objects over Entities" guideline. It is applicable when treating some concept in your domain model as Value Object is not a feasible task due to limitations imposed by your database.

To implement it in practice, make sure you:

  • Keep the wrapping entity thin. It should only hold the identity imposed by the database.

  • Transfer all domain logic from that entity to the nested value object.

  • Don’t expose the entity outside the aggregate. Clients of the aggregate should work directly with the value object.

Subscribe


I don't post everything on my blog. Don't miss smaller tips and updates. Sign up to my mailing list below.

Comments


comments powered by Disqus