I tried to combine two rules.
1. Don't fall to primitive obsession (e.g. EMailAddress should be a value object in domain objects and not a string).
2. Don't use value objects in commands and events.
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.
- 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
ApplyChange(new
EMailAddressChanged(???));
}
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?