The journeylism of @yreynhout

On CQRS, DDD(D), ES, …

Value objects in an event sourced domain model

A question that comes up from time to time is what role value objects can play in an event sourced domain model and how they contribute to and interact with the events produced by said domain model. Value objects come in many shapes and sizes, but obvious ones like money (amount and currency) and period (range of time values) come to mind. Fields that change together or are used together in the ubiquitous language are prime suspects of information “clustering” and can usually be represented as value objects when they clearly lack any form of identity. They are an ideal place to DRY up some of the validation, security and contextual capturing code that surrounds these fields.
Below you’ll find a stripped version of a value object from my own domain. Things you can’t easily spot from the code below is the clusivity of the lower and upper boundary of this range-like value type, partially because I omitted the arithmetic operations. The finer details, such as which methods are required and who to collaborate with, surface as you discuss them with domain experts.

namespace Domain {
  public class RolloutPeriod {
    readonly DateTime _from;
    readonly DateTime _to;

    public RolloutPeriod(DateTime from, DateTime to) {
      if(from > to)
        throw new ArgumentException("The from value of the rollout period must be less than or equal to the to value.", "from");
      _from = from;
      _to = to;
    }

    //Arithmetic members omitted for brevity

    public bool Equals(RolloutPeriod other) {
      if (ReferenceEquals(null, other)) return false;
      if (ReferenceEquals(this, other)) return true;
      return other._from.Equals(_from) && other._to.Equals(_to);
    }

    public override bool Equals(object obj) {
      if (ReferenceEquals(null, obj)) return false;
      if (ReferenceEquals(this, obj)) return true;
      if (obj.GetType() != typeof (RolloutPeriod)) return false;
      return Equals((RolloutPeriod) obj);
    }

    public override int GetHashCode() {
      return _from.GetHashCode() ^ _to.GetHashCode();
    }
  }
}

Something you do notice in the above code is that there are no getters nor setters. This is deliberate. On one hand, it allows me to redefine how I represent values on the inside (i.e. the from and to value). On the other hand, I decouple any consuming code from the internals and gently steer that code into a Tell Don’t Ask style of interaction. It makes you think about the question: “Why do you want access to my internals? What is it that you want to achieve with them? Just tell me what you want to do.”.
Once you get that right, you bump into the next hurdle: using value objects in an event sourced domain model.

namespace Domain {
  public class Schedule : AggregateRootEntity {
    public void Rollout(RolloutPeriod period) {
      //Guards ommitted for brevity

      ApplyEvent(Build.RolledoutSchedule.ForPeriod(period));
    }
  }

  public abstract class AggregateRootEntity {
    Guid _id;
    long _version;

    protected void ApplyEvent(IEventBuilder<IEvent> builder) {
      var @event = builder.Build(_id, _version++);
      //The usual stuff as found below goes here
      //https://github.com/gregoryyoung/m-r/blob/master/SimpleCQRS/Domain.cs
    }
  }
}

namespace Messaging {
  public static class Build {
    public static RolledoutScheduleBuilder RolledoutSchedule { get { return new RolledoutScheduleBuilder(); } }
  }

  public class RolledoutScheduleBuilder : IEventBuilder<RolledoutScheduleEvent> {
    RolloutPeriod _rolloutPeriod;

    public RolledoutScheduleEvent Build(Guid id, long version) {
      return new RolledoutScheduleEvent(id, version, _rolloutPeriod);
    }

    public RolledoutScheduleBuilder ForPeriod(RolloutPeriodBuilder builder) {
      _rolloutPeriod = builder.Build();
      return this;
    }
  }

  public interface IEventBuilder<out TEvent> where TEvent : IEvent {
    TEvent Build(Guid id, long version);
  }

  public class RolledoutScheduleEvent : IEvent {
    public RolledoutScheduleEvent(Guid id, long version, RolloutPeriod period) {
      Id = id;
      Version = version;
      Period = period;
    }

    public RolloutPeriod Period { get; private set; }

    public long Version { get; private set; }

    public Guid Id { get; private set; }
  }

  public interface IEvent { }

  public class RolloutPeriod {
    public DateTime From { get; private set; }
    public DateTime To { get; private set; }

    public RolloutPeriod(DateTime from, DateTime to) {
      From = from;
      To = to;
    }
  }

  public class RolloutPeriodBuilder {
    DateTime _from;
    DateTime _to;

    public RolloutPeriodBuilder From(DateTime value) {
      _from = value;
      return this;
    }

    public RolloutPeriodBuilder To(DateTime value) {
      _to = value;
      return this;
    }

    public RolloutPeriod Build() {
      return new RolloutPeriod(_from, _to);
    }
  }
}

The rollout period passed into the Schedule rollout method, is a value object part of the domain model. The rollout period of the event – a bit obfuscated by the syntax ‘Build.RolledoutSchedule.ForPeriod’ above – is part of the messaging bits. How do these two meet without exposing internals?

One thing I’d like to make clear: the domain value object IS NOT the data structure used inside the event. It’s a different type.

By applying double dispatch to the domain rollout period and adding a builder extension method – inside the domain model – that is domain rollout period aware, we get a nice translation going.

namespace Domain {
  public class RolloutPeriod {
    //... see above for other members

    internal void BuildValue(RolloutPeriodBuilder builder) {
      builder.From(_from).To(_to);
    }
  }

  public static class BuildExtensions {
    public static RolledoutScheduleBuilder ForPeriod(this RolledoutScheduleBuilder builder, RolloutPeriod period) {
      var valueBuilder = new RolloutPeriodBuilder();
      period.BuildValue(valueBuilder);
      builder.ForPeriod(valueBuilder);
      return builder;
    }
  }
}

The reverse, going from an event (or data structure within that event) to a value object, is just as easy.

namespace Domain {
  public class RolloutPeriod {
    //... see above for other members

    internal static RolloutPeriod FromEvent(Messaging.RolloutPeriod period) {
      return new RolloutPeriod(period.From, period.To);
    }
  }

  public class Schedule : AggregateRootEntity {
    void Apply(RolledOutScheduleEvent @event) {
      _rolloutPeriod = RolloutPeriod.FromEvent(@event.RolloutPeriod);
      //...
    }
  }
}

So, really, there’s no excuse to NOT use value objects inside an event sourced domain model nor to expose the internal state of those value objects for event sourcing purposes. Happy coding …

P.S. More information on event builders can be found here.

About these ads

3 Responses to Value objects in an event sourced domain model

  1. Yoann June 20, 2012 at 07:53

    Nice post Yves. one thing though, I did not get clearly. you add :
    internal void BuildValue(RolloutPeriodBuilder builder) {
    builder.From(_from).To(_to);
    }
    to RolloutPeriod and it makes sense to add a behavior to create the value object on the messaging side since it’s a general concern for RolloutPeriod. But if I had to use this value object in some specific AR to make validation between these dates (to and from) and another date my AR might have. I would have to create a new RolloutPeriod for this specific class and add a method to put this specific validation in this new ValueObject? is this how you do? or am I missing something?
    And thanks for this share and your tweets, it is nice to have a glimpse of your whereabouts, it gives nice hints … ;)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 607 other followers

%d bloggers like this: