I tried to combine two rules.
- Don't fall to primitive obsession (e.g. EMailAddress should be a value object in domain objects and not a string).
- Don't use value objects in commands and events.1, 2, 3
If commands and events should not contain value objects (for obvious and often discussed reason) they contain something as " EMailAddress" as string. In the aggregate it should be handled as an EMailAddress value object with it's own constraints and checking.
So far, so good.
But where does the conversion happen? Do we call a "void DoIt(string eMailAddress)" method on the aggregate root and one of its first actions is to convert the string into an EMail value object? Or does the CommandHandler do the conversion and the aggregate root method looks like "void DoIt(EMailAddress eMailAddress)"? Solution 2. seems more elegant, because it holds the aggregate root free of primitives and conversions. But what about the events and even more interesting raising the event?
Example AggregateRoot Method with solution 2:
public void ChangeEMailAddress(EMailAddress eMailAddress )
//Do some interesting stuff without changing state
The ??? is the problem. If the event should not contain value objects the EMailAdress value object must be converted back into a string. Is a (second) constructor of the event (that takes the value object) allowed to do it, to keep the aggregate free of the conversion?
And what about the handling of the event in the aggregate root? In Simple CQRS Style it would be like this:
private void Apply(EMailAddressChanged e)
this.eMailAddress = new EMailAddress(e.eMailAddress);
So the conversion is again in the aggregate. We could also use EventHandlers here that do the conversion and then the method will look like:
private void EMailAddressChanged(EMailAddress eMailAddress)
this.eMailAddress = eMailAddress ;
(Yes I know thats simple CRUD stuff that maybe should not done the whole CQRS/ES way at all, assume that some logic is involved)
But there is one drawback here. In either solution, the conversion could fail. But an event should never fail. If I change the validation in the constructor of EMailAddress it is possible (if I use event sourcing) that older events can no longer be processed. So should I have 2 constructors/static create methods on my value objects? One with and one without validation?
- Where do you do the conversion from primitive to value object and back in the 3 cases Handling of the Command, Raising of the Event, Handling of the event?
- How do you deal with value objects in event sourcing to ensure its constructor validation do not fail for older events?
I found Where to convert primitive types in meaningful types in Clean Architecture / Onion Architecture and it's partly related, but does not answer my question.
To make my question more clear, assume the following Structure:
- A command comes accross the wire. It should be a stable contract that should not change if I change my domain (write) model. So it should not contain any value objects from it. So it contains kind of dtos instead of value objects or the value object is mapped to multiple fields with primitive types.
- The command is taken by a command handler in the application layer. This loads the aggregate from a repository, dispatch the command to the aggregate root and then stores the aggregate in the repository. When dispatching the command to the aggregate root, a conversion has to be done from primitive types to value objects cause I don't want that conversion to be done in the aggregate root. It's no domain stuff.
- The aggregate root makes some validations (is the operation valid in the current state), calculations, calls DateTime.Now or whatsoever. Then it fires an event. THIS IS MY MAIN PROBLEM.
- An event handler takes the event and applies it to the aggregate root. Here again the primitive types/dtos must be converted to value objects cause I do not want to do that in the aggregate root.
- The event is published and stored and should not contain any value objects from the domain because of the same reasons as the commands. They are a contract for e.g. the projections of the read model or other event consumers.
The conversion of the commands and events primitive types to value objects could be done in the handlers. But what about the CREATION of the event in the aggregate root?
I see 2 possible solutions:
- The creation (and publishing) of the event is done by some kind of injected abstract factory that takes value objects and converts them into the events.
- The aggregate root creates and publishes events with value objects and this events are then converted to the domain value object free events outside of the aggregate.
While 1. makes you implement a complex factory, 2. implies implementing all events in 2 versions plus a converter between them.
If we go with 2. I had the additional idea to make a whole second layer of (domain)commands(and handlers) and (domain)events(and handlers). A layer of commands and events that contain domain value objects and are dispatched/created to/by the aggregate root and in the applicaionlayer converted to "contract" commands/events without any (write) model value objects.
Is there any other solution/simplification? Which way would you go?