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:

Nesting a Value Object inside an Entity: 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:

Nesting a Value Object inside an Entity: 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.

Related articles:

Share




  • Michael G.

    Great article. I hadn’t considered to use a wrapper in such a fashion, but now that I’ve read it, it makes sense to do so, rather than treat the entire object as an Entity.

    How does this translate to relations with composite keys?
    Considering a permission system for a forum, there are usually Groups and Members, each with a set of Permissions.

    Permissions
    MemberId
    GroupId
    ForumId
    CanCreatePost
    CanEditPost
    CanDeletePost

    The Permissions (ForumId + CanX) are clearly a ValueObject, which applies to a combination of MemberId and GroupId, with either of those being nullable, but not both.

    Considering that a Member can belong to one or more Groups, would it still be feasible to use the entity wrapper?

    PS: I consider ForumId part of the Permission ValueObject in this context, since it doesn’t identify a single Permission, but is merely data.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Considering that a Member can belong to one or more Groups, would it still be feasible to use the entity wrapper?

      I don’t see why not. You could gather those permissions and expose them as a single collection of value objects. A good part here is that you can implement some sophisticated merge logic, for example squash 3 permissions with different CanX under a single one, and the client code doesn’t have to deal with it on its own.

      • Michael G.

        Great! Now I just need to figure out how to do so, while maintaining proper separation of concerns. But that is a self-educational task 🙂

        More and more I find myself using ValueObjects in code, and it is, so far, much easier to maintain and understand than bloated Entities.

        Thank you for the constant stream of useful articles on the subject.

  • E.

    Thanks for your excellent explanation and good timing because I’m struggling with a related problem.
    In my case I have and Address that is related to Person and Company.
    In both relations I need to keep track of the history (DateFrom and DateTo) for the possible changing address locations.
    In my model, I need one unique addressId for each period in time.
    Is it advisable to use Address as an entity with AddressDetail as value object?
    And in the corresponding History tables I use AddressDetail and DateRange as value objects?
    Or do you have a better approach?

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Yes, this implementation looks good, especially if you are able to hide the entity that is responsible for the history, so that the client code doesn’t see it. Also, try to unify the history and the address entities by nesting the AddressDetail value object in both of them.

      So, in Person or Company, you can have a CurrentAddress property that returns the AddressDetail value object, and you also can introduce a GetAddressRecord method that would accept a date, search among the history entities and also return an AddressDetail.

      DateRange as a separate value objects is almost always a good design decision.

  • http://buclenet.com Albert Capdevila

    Excellent! Thanks!