Primitive obsession, value objects, Commands, Events and where to convert

334 views
Skip to first unread message

hothi...@googlemail.com

unread,
Oct 21, 2020, 1:57:31 AM10/21/20
to DDD/CQRS
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.

But where does the conversion happen?
  1. 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?
  2. 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?

So:

  1. 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?
  2. How do you deal with value objects in event sourcing to ensure its constructor validation do not fail for older events?

mynkow

unread,
Oct 21, 2020, 2:18:49 AM10/21/20
to DDD/CQRS
I totally disagree with avoiding VOs in Events and Commands. People tend to avoid it because of technical reasons, serialization and bad design.

By removing the VOs you are limiting your domain language and you create accidental complexity which brings your question in front.

hothi...@googlemail.com

unread,
Oct 21, 2020, 2:52:32 AM10/21/20
to DDD/CQRS
That seems to be discussed very controversal.

My main reason to avoid them is decoupling. If I have a backend Web Api and I have clients/consumer I have no control about, then the commands (and maybe events) are part of the contract.
If I want to change the inner structure of my aggregate and e.g. want to change the implementation of a value object, this would break the contract and break all clients, because this change will change the commands that use that value object.

In my opinion the developer should not have to be aware of this and not been discouraged to refactor the domain model.

Peter Hageus

unread,
Oct 21, 2020, 3:36:00 AM10/21/20
to ddd...@googlegroups.com
The domain should only accept VO’s, it’s the command handlers job to prepare a command (load aggregate, cast/build proper arguments etc).

(The exception is the event handler inside the aggregate, it also needs to cast from primitives).

It’s important to distinguish between hydrating/deserializing a value object from storage/dto and constructing it based on input/command. Hydrating should bypass all validations, as it’s an known fact that it has been valid in the past.

I tend to use implicit conversion from VO to primitive if it’s a one value (Id’s, email etc).

If it’s more complex I have a XxxxContract VO in the same assembly/namespace/module/whatever as my events. It’s a lot object without any validation. (I dislike the term Dto in code, since it’s purely technical concern, and usually go with Contract instead. But it’s the same principle)

/Peter


--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/3680e58f-357a-46f4-a12e-bd27015570fdn%40googlegroups.com.

mbneto

unread,
Oct 21, 2020, 7:41:17 AM10/21/20
to ddd...@googlegroups.com
The decoupling reason for me is the main one. Since Commands and Events are simply data structures with an explicit intent they are supposed to be devoid of behavior.

Not only this makes it easier for the whole serialization/hydration as it prevents you from exposing domain concepts at the UI layer where someone could call the entity to make changes outside your core. That is why my Command/Query handlers never receive or return entities or VOs.

Again, practicality may get in and you decide to take shortcuts that make sense for you. Example, your stack uses the same language across the board.

João Bragança

unread,
Oct 21, 2020, 11:56:02 AM10/21/20
to ddd...@googlegroups.com
There are two ways to do this really. Either via an explicit cast or a ToXXX (e.g., ToString) on your value object.





--

mynkow

unread,
Oct 23, 2020, 4:01:55 PM10/23/20
to DDD/CQRS
If you are talking about clients who are using your API here is how I do it...

WebApi has its own contract with pure primitives as input and its own structure in terms of json objects. It is a responsibility of the WebApi itself to internally transform this into a Command with VOs if any. Now you are free to modify the command and even replace it entirely without breaking the clients. Same for the API, you could introduce V2 of the API but internally it will send the correct commands.

mynkow

unread,
Oct 23, 2020, 4:04:20 PM10/23/20
to DDD/CQRS

Rickard Öberg

unread,
Oct 23, 2020, 8:35:58 PM10/23/20
to ddd...@googlegroups.com
I use a similar strategy, with one major difference: the Web API starts by exposing the aggregate commands straight, if possible, and only have their own versions or conversions if needed. So in 95%+ of cases I only need the internal commands, but with an API override where needed. To avoid doing unnecessary work.

Sent from my iPad

On 24 Oct 2020, at 04:02, mynkow <myn...@gmail.com> wrote:

If you are talking about clients who are using your API here is how I do it...
--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.

Greg Young

unread,
Oct 23, 2020, 9:10:47 PM10/23/20
to ddd...@googlegroups.com
Is your value object in a message related to the one in your domain?

Very often your exterior message looks quite different than your internal structure (and you often have multiple messages with the same intent).

I have seen many build small helper objects on top of their messages but using (especially sharing) domain objects can run into many issues.

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.


--
Studying for the Turing test

Nikolai Mynkow

unread,
Oct 24, 2020, 3:43:54 PM10/24/20
to ddd...@googlegroups.com
Well, it is a yes and no. My strategy is to use VOs in my events and commands. However these events and commands are internal to the bounded context where they live. If another system or bounded context wants to know what is happening I use published language. The idea is that the PL does not change often. When something significant happens in the current bounded context you could fire a PUBLIC EVENT. Maybe here is the conversion which the others are doing...
-------------------------------------------------
N.Mynkow (myn...@gmail.com)


You received this message because you are subscribed to a topic in the Google Groups "DDD/CQRS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/dddcqrs/rvZi6_ACKwE/unsubscribe.
To unsubscribe from this group and all its topics, send an email to dddcqrs+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/CAC9RQtitgAX_HBxfWZVf5WdHZXYPJpk4%3DxqW8vkR%2BnwnbqF3hw%40mail.gmail.com.
Reply all
Reply to author
Forward
0 new messages