Why we Avoid Putting Value Objects in Events

What’s the harm in putting Value Objects in your Events? Say we have the following event:

public class PlannedNewRoastDay : Message
{
    public readonly Guid Id;
    public readonly string RoastDate;

    public PlannedNewRoastDay(Guid id, string roastDate)
    {
        Id = id;
        RoastDate = roastDate;
    }
}

Being used by the RoastDay Aggregate (see earlier posts about First Pop Coffee Company):

public class RoastDay : AggregateRootEntity
{
    public RoastDayId RoastDayId { get; private set; }
    public RoastDate RoastDate { get; private set; }

    private RoastDay()
    {
        Register(@event =>
        {
            RoastDayId = new RoastDayId(@event.Id);
            RoastDate = RoastDate.FromDateString(@event.RoastDate);
        });
    }

    public static readonly Func Factory = () => new RoastDay();

    public static RoastDay PlanRoastDayForDate(Guid roastDayId, string roastDate, string todaysDate)
    {
        if (roastDayId == Guid.Empty || roastDayId == null) throw new ArgumentNullException(nameof(roastDayId), $"{nameof(roastDayId)} must be provided");
        if (roastDate == null) throw new ArgumentNullException(nameof(roastDate), $"{nameof(roastDate)} must be provided");
        roastDate.EnsureIsValidDateString($"{nameof(roastDate)}");
        todaysDate.EnsureIsValidDateString($"{nameof(todaysDate)}");
        if (roastDate.IsEarlierThan(todaysDate)) throw new ArgumentException($"{nameof(roastDate)} cannot be earler than {nameof(todaysDate)}");

        var roastDay = new RoastDay();
        roastDay.ApplyChange(new PlannedNewRoastDay(roastDayId, roastDate));
        return roastDay;
    }

Here is the RoastDate Value Object:

public class RoastDate : ValueType<RoastDate>
{
    public readonly LocalDate Date;

    private RoastDate(LocalDate date)
    {
        Date = date;
    }

    public static RoastDate FromDateString(string dateString)
    {
        var dateStringAsLocalDate = dateString.ToLocalDate();
        return new RoastDate(dateStringAsLocalDate);
    }

    protected override IEnumerable<object> GetAllAttributesToBeUsedForEquality()
    {
        return new List<object>() { Date };
    }
}

We can see that we are passing new PlannedNewRoastDay(roastDayId, roastDate) into the Aggregate Root base class’s ApplyChange method, which is then handled by the event handler for PlannedNewRoastDay which was registered in the private constructor.

If the event handler for PlannedNewRoastDay changes the state of the Aggregate by assigning new instances of the RoastDayId and RoastDay Value Objects to those fields, why can’t we simply pass them inside the Event?… like this:

public class RoastDay : AggregateRootEntity
{
    public RoastDayId RoastDayId { get; private set; }
    public RoastDate RoastDate { get; private set; }

    private RoastDay()
    {
        Register(@event =>
        {
            RoastDayId = @event.RoastDayId;
            RoastDate = @event.RoastDate;
        });
    }

    public static readonly Func Factory = () => new RoastDay();

    public static RoastDay PlanRoastDayForDate(Guid roastDayId, string roastDate, string todaysDate)
    {
        if (roastDayId == Guid.Empty || roastDayId == null) throw new ArgumentNullException(nameof(roastDayId), $"{nameof(roastDayId)} must be provided");
        if (roastDate == null) throw new ArgumentNullException(nameof(roastDate), $"{nameof(roastDate)} must be provided");
        roastDate.EnsureIsValidDateString($"{nameof(roastDate)}");
        todaysDate.EnsureIsValidDateString($"{nameof(todaysDate)}");
        if (roastDate.IsEarlierThan(todaysDate)) throw new ArgumentException($"{nameof(roastDate)} cannot be earler than {nameof(todaysDate)}");

        var roastDay = new RoastDay();
        roastDay.ApplyChange(new PlannedNewRoastDay(new RoastDayId(roastDayId), RoastDate.FromDateString(roastDate));
        return roastDay;
    }

Events are immutable. This is the most important reason for keeping Value Objects out of our Events. Logic can be added, field names can be changed, etc. in Value Objects, but not for Events. This is why versioning is important for Event Sourced systems. Value Objects, like Entities, have the main responsibility of protecting invariants and ensuring that the Domain Model is always in a valid state. Events, are facts that are telling us that a change did happen and that the Domain Model protected its invariants and proceeded with a change.

Domain Models are meant to change, so changing the behavior of a Value Object in the Domain Model shouldn’t, then, affect the history of things that have happened in the business before this change. The Domain Model is mutable, therefore having Events take dependencies on the Domain Model means that Events must become mutable - which they are not. If the Domain Model was mutable, we’d also need versioning it - having classes like RoastDate_v2… this doesn’t match the Ubiquitous Language.

What are the technical implications of this rule? As your domain model evolves, you may add new invariants to be checked, or change existing ones on the Value Object that you’re serializing to the Event Store.

When we replay these events to reconstitute the state of an Aggregate, if the Value Object has a new invariant that makes the Event invalid - how can we deserialize the Event into a Domain Model which guarantees its state to be valid at all times? We can’t.

We can’t simply decide not to deserialize an Event because the Value Object’s new invariant(s) make it exist in an invalid state.

So instead, if we only pass the data needed to carry out an activity on the Domain Model, i.e. an Event with mostly primitive types we can handle the different versions of our Events without making them un-deserializable.

Given that a later Event version “must be convertible from the old version of the event” (Young, 12), as long as we haven’t made our Events dependent on our Domain Model - we can take the primitive properties from either event version and handle them appropriately in our Domain Model.

Summary

We just quickly went over the “Why” behind the rule that we should avoid including Value Objects (and other Domain Model objects) in Events. It comes from the fact that Events are immutable and they are facts that something truly did occur in the business. We can’t alter Events as our Domain Model evolves, which is why we must version our Events. But our Domain Model is mutable, and this is why we can’t make our Events dependent on Domain Model objects.

Please remember to click the red button below to sign up for my mailing list, I have a huge backlog of topics to write about and some are exclusive to my mailing list. Don’t miss out!


Sources

Tweet
comments powered by Disqus