3

Is there a place for value objects in an event sourced domain model?

Lets define a value object as an object with immutable state that guards its invariants and has no particular identifier.

An event sourced domain model in this context is a domain that is entirely or partially event sourced, meaning that its current state can be derived from applying all events that have occurred in the past. Events themselves are considered immutable, even over time.

Debate has taken place about the validity of using value objects within events - this question goes slightly further: Do value objects have a place in event sourced domains at all?

The (potential) problem with using value objects is that it becomes rather tricky to alter the domain in such a way that invariants are tightened.

An example of this scenario would be to have a Username value object, with the sole constraint that the name must be anywhere between 2 and 16 characters.

While this has been working well for some time, the business decides to only allow usernames of at least 5 characters. A migration period begins and users with names of less than 5 characters are asked to update their names.

Lets say the process was successful, correction events are applied and everyone is happy. We tighten the constraints on our Username value object to require at least 5 characters.

For a while everyone is happy, but then we discover a problem with the snapshots and replay all events.

We now face an exception from our Username object: by loading the historic data, we're breaking an invariant of our domain.

The rules of a value objects apply retroactively - does this make them inherently unsuitable for event sourcing? Would it be worth applying versioning of value objects? Is there a simpler way of avoiding such problems?

Stratadox
  • 1,291
  • 8
  • 21
  • "The rules of a value objects apply retroactively" - I don't get that part. What do you think is specific to VOs in that regard that would make them different from entities/non-value domain objects? – guillaume31 Sep 17 '18 at 13:21
  • The part about invariants reminds me of [this blog post](https://thinkbeforecoding.com/post/2013/07/28/Event-Sourcing-vs-Command-Sourcing). – guillaume31 Sep 17 '18 at 13:43
  • @guillaume31 Retroactivity in this context means that the rules would also be applied to data from events in the past. The previous events were valid with the old version of the value object, but the update in the value object makes the past events "illegal" in the eyes of the new code. Other objects don't have this problem unless they validate their state upon construction. – Stratadox Sep 17 '18 at 13:59
  • Validating integrity at instantiation is a perfectly valid and normal thing to do for entities... See http://codebetter.com/gregyoung/2009/05/22/always-valid/ – guillaume31 Sep 17 '18 at 14:02
  • Plus, even if not on construction, if I understand your premise correctly, all invariants still have to be checked when the entity transitions to another state... so also when events are replayed, right? I'm really not sure about the distinction you make between entity invariants and VO rules in that regard. – guillaume31 Sep 17 '18 at 14:06
  • 1
    I suppose the question touches on all objects that validate their state when applying all past events. I mentioned value objects by name because those are the most common use case, and because they are the subject of the debate this question is derived from. – Stratadox Sep 17 '18 at 14:33
  • My suggestion would be "don't validate domain rules when applying past event" At least not for half-domain half-applicative rules like max string length that don't have much impact on the integrity of an aggregate anyway. – guillaume31 Sep 17 '18 at 15:07
  • The name length is a simplified example; it could also apply to much more domain specific rules like a raised minimum deposit or a deprecated game character. – Stratadox Sep 17 '18 at 20:41
  • The suggestion "don't validate domain rules when applying past event" seems to indicate that value objects have no place in event sourcing, since especially those always validate their invariants upon (re)construction. If such is indeed your perspective, I sincerely welcome an answer from that direction. – Stratadox Sep 17 '18 at 21:25
  • 1/ Again, I can't see a fundamental difference between value objects and say, child entities there. 2/ while DDD literature insists on what VOs should do when their public facing part is solicited, and you're correct in saying they should check their integrity on creation, it rarely talks about how to rehydrate them from a persistent store, leaving that to the implementor and the specific persistence technology they're using. But it is precisely what your Q is about - replaying events to build up an aggregate from stored events, and may follow a totally different code path. – guillaume31 Sep 18 '18 at 08:14
  • 3/ Yes, some of the DDD "luminaries" don't validate domain rules when applying an event - whether it news up a VO or not. See [here](https://github.com/gregoryyoung/m-r/blob/master/SimpleCQRS/Domain.cs) and [there](https://github.com/VaughnVernon/IDDD_Samples/blob/master/iddd_collaboration/src/main/java/com/saasovation/collaboration/domain/model/calendar/Calendar.java) and this Greg Young [video](https://www.youtube.com/watch?v=whCk1Q87_ZI). – guillaume31 Sep 18 '18 at 08:21
  • 4/ I'm more familiar with functional implementations of CQRS/ES where the lines between entity and VO are more blurred. Will give a shot at answer though. – guillaume31 Sep 18 '18 at 08:28

4 Answers4

5

I would say, that at the moment you redefined what Username means, and you don't migrate historical data somehow, you've essentially created 2 different Username meanings.

Because there are 2 different meanings of the word, you have to make it explicit in the code somehow. "Versioning" is one way, although I wouldn't use such a generic solution, there are different modeling options.

You could make it explicit that the history of a "username" is just that, a history. So for example create a HistoricUsername, which is the event-sourced object, even a value object if you want. And create a Username which is at all times the username with the most current rules, which is not persisted at all, but created from a HistoricUsername if it can.

Some people suggest sometimes to extract the "rules" from the object, and re-apply it later. That way the object itself is valid at all times and you can ask it to validate itself against rules that might change. I don't really prefer these kinds of solutions, but it's an option, and the Username would still be a value-object.

So the problem is not really that value-objects don't fit into event-sourcing, it's just that the modeling has to be more accurate.

Robert Bräutigam
  • 7,514
  • 1
  • 20
  • 38
  • Thanks for the clear answer. I like the approach of the Historic versions, it keeps the domain reconstitutable while at the same time preserving the ubiquitous language. I'm wondering how you'd suggest to deal with relations to that object: lets say my entity `User` has a `name()` method that retrieves the VO, I cannot typehint for either VO without a potential type error. Adding an interface for `Username`s might seem overcomplicated though. – Stratadox Sep 17 '18 at 09:48
  • @RobertBräutigam "Some people suggest sometimes to extract the 'rules'" +1, as reflected by the Decide part [in this schema](http://devlyon.fr/mixter/slideResources/eventsourcing.jpg) – guillaume31 Sep 17 '18 at 13:28
  • Also here: http://blog.leifbattermann.de/2017/04/21/12-things-you-should-know-about-event-sourcing/ – guillaume31 Sep 17 '18 at 13:40
4

We've solved this in a slightly different way. By separating the public API of our value objects from the internal (domain only) API, we are able to evolve one without affecting the other.

For example:

public class Username
{
    private readonly string value;

    // Domain-only (internal) constructor.
    // Does not enforce constriants and can only be called within the domain.
    internal Username(string value)
    {
        this.value = value;
    }

    // Public factory method.
    // Enforces business constraints. Used by consumers of the domain (application layer etc.)
    // to create new instances of the value object.
    public static Username Create(string value)
    {
        // Business constraints. These will evolve and grow over time.
        if (value == null)
        {
            // throw exception etc.
        }

        if (value.Length < 2)
        {
            // throw exception etc.
        }

        return new Username(value);
    }
}

Consumers of the domain must use the static Create method to create a new instance of the value object. This factory method contains all of our business constraints and prevents an instance being created in an invalid state.

Inside the domain, classes have access to the internal (constraint-less) constructor. Since this does not enforce any business constraints, an instance of the value object can always be created in this way (regardless of its value). By using this constructor when replaying events we can ensure that historical data will always succeed.

The benefits of this design are:

  • A single class is used to represent the domain concept (no need for multiple classes, versioning etc.).
  • Business rules are free to evolve over time.
  • Historical data always works. A Username from a year ago is still a user name, even if our rules have changed.
Alex Justus
  • 41
  • 1
  • 3
  • Nice and clean - the validation is now (at this moment), but the re-playing of old events we're valid too (at that time) - so no need to re-validate. They are already DONE. – Tony Trembath-Drake Nov 15 '18 at 02:19
  • @Greg Young: Doesn't the `UserName` represents it's meaning by the constraints being checked while it's being created? Does't the aggregate state leak into the domain event? Should't the aggregate state be separated from the domain event? – Mohsen Aug 07 '19 at 11:29
3

Do value objects have a place in event sourced domains at all?

Yes.

Is there a simpler way of avoiding such problems?

"Don't do that."

The problem you are describing is really one about messaging - if we make backwards incompatible changes to our messages, then things break.

(More precisely, you have a "Username" message, and you are trying to re-use that message with a new set of constraints that reject some previously valid uses of the message).

The answer is that you don't introduce backwards incompatible changes - instead, introduce new names that match the new requirements, and deprecated the old ones.

Which is to say, adding support for new messages, and removing support for the old messages, become two separately managed options.

Greg Young's book Versioning in an Event Sourced System dedicates some chapters to this idea. Also, Rich Hickey ends up touching on these important ideas in most of his talks -- I'd suggest starting from Spec-ulation.

The "value object", meaning that the type that the current implementation of the domain model uses to move the information around, is a separate concern from the messages. The data structures we use in memory don't need to be coupled to our serialization formats.

The representation of the information on the wire is distinct from the representation of information in memory, and that in turn is distinct from the abstractions that manipulate the information in memory.

The challenging thing is that, at the beginning of a project, you have the least amount of information about when the different representations are going to diverge.

VoiceOfUnreason
  • 52,766
  • 5
  • 49
  • 91
  • Interesting resources, I'll be sure to check them out! One thing I'm slightly concerned about is the impact of changing the name of a value object on the ubiquitous language. Are there generally accepted ways of dealing with such changes in that context? – Stratadox Sep 17 '18 at 09:34
2

Although already answered I do find this an interesting situation.

I agree with others that the event data should be record-based and, therefore, nothing more than a data container that may be used to reconstitute the aggregate.

That being said when the rules change so does the domain. A major portion of domain-driven design is to capture as much of the domain (rules/structure) as is required. If this is the case should the changes in the rules not also be kept?

For instance, if we have a Username Value Object and it starts out with the 2 to 16 characters rules then that is coded as such:

public class Username
{
    public string Value { get; }

    public Username(string value)
    {
        if (value.Length < 2 || value.Length > 16)
        {
            throw new DomainException("Username must be between 2 and 16 characters");
        }

        Value = value;
    }
}

Now we get to 1 March 2018 and the rule changes. We can keep the rule around:

public class Username
{
    public string Value { get; }

    public Username(string value, DateTime registrationDate)
    {
        if (registrationDate < new Date(2018, 3, 1) &&
            (value.Length < 2 || value.Length > 16))
        {
            throw new DomainException("Username must be between 2 and 16 characters");
        }

        if (registrationDate >= new Date(2018, 3, 1) &&
            (value.Length < 5 || value.Length > 16))
        {
            throw new DomainException("Username must be between 5 and 16 characters");
        }

        Value = value;
    }
}

That is the basic idea. In this way we keep our "old" rules around as well. This may become quite a hassle but I don't have enough experience to say. Changing our rules retroactively may introduce some pretty tricky situation so I guess one would need to evaluate this on a case-by-case basis.

Just a thought.

Eben Roux
  • 12,983
  • 2
  • 27
  • 48
  • Thank you for sharing your perspective on the matter! It's an interesting solution to keep both validation circumstances within the value object. I'm not entirely sure if I like that our `Username` now needs to know about a `RegistrationDate` (it used to be just a name..) but it certainly solves the retroactiveness of the validation rules. – Stratadox Sep 17 '18 at 09:39