Value objects in domain events and versioning

322 views
Skip to first unread message

Alexandre Potvin Latreille

unread,
Aug 25, 2016, 3:26:35 PM8/25/16
to DDD/CQRS
I see that in the Vaughn Vernon IDDD sample domain events are encapsulating value objects.

Is that recommended?
What would that mean for versioning?
Would we have to version value objects as well, I guess we would?

João Bragança

unread,
Aug 26, 2016, 7:35:57 AM8/26/16
to ddd...@googlegroups.com
Don't. Instead, use some method or operator on the value object to turn it back into a primitive / message part.

--
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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--

Greg Young

unread,
Aug 26, 2016, 12:02:26 PM8/26/16
to ddd...@googlegroups.com
Using value objects on events (or DTOs) tends to be a bad idea
specifically for the reason you mention (versioning). Renaming
something in your model causing a breaking protocol change is
generally a bad idea.
> --
> 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.
> For more options, visit https://groups.google.com/d/optout.



--
Studying for the Turing test

Alexandre Potvin Latreille

unread,
Aug 26, 2016, 3:28:31 PM8/26/16
to DDD/CQRS, joao...@braganca.name
The thing I'm not sure of is that even though you haven't stored the value objects directly in the event, they do have to be reconstructed from the state of the event. Therefore, you aren't entirely free from an incompatibility between an event that encapsulate the state of an old value object version (as a set of primitives) and the new value object version. Which part of the code should be responsible to resolve the incompatibility? Would I have something like a `reconstitute` static factory method on every value objects which takes a set of primitives and deal with various versions?

On Friday, August 26, 2016 at 7:35:57 AM UTC-4, João Bragança wrote:
Don't. Instead, use some method or operator on the value object to turn it back into a primitive / message part.
On Thu, Aug 25, 2016 at 9:26 PM, Alexandre Potvin Latreille <alexandre.pot...@gmail.com> wrote:
I see that in the Vaughn Vernon IDDD sample domain events are encapsulating value objects.

Is that recommended?
What would that mean for versioning?
Would we have to version value objects as well, I guess we would?

--
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.

For more options, visit https://groups.google.com/d/optout.

Greg Young

unread,
Aug 26, 2016, 4:15:31 PM8/26/16
to ddd...@googlegroups.com, João Bragança
In the scenario you describe at least they change independently. This
is the same discussion as entities over the wire.

On Fri, Aug 26, 2016 at 8:28 PM, Alexandre Potvin Latreille

João Bragança

unread,
Aug 26, 2016, 5:28:37 PM8/26/16
to ddd...@googlegroups.com
they do have to be reconstructed 

use a constructor

To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Alexandre Potvin Latreille

unread,
Aug 26, 2016, 10:33:53 PM8/26/16
to DDD/CQRS, joao...@braganca.name
Greg: Yeah, this is what I was telling myself as well. It's the same problem with any kind of object that has to be reconstructed from some persisted state. The difference is is that in most system I've worked on we could just perform a scheduled maintenance process where we would upgrade the persisted states to match the new model. I'm still not used to techniques allowing to evolve the model while preserving the immutability of the persisted state.

 João: How do you prevent this constructor to be used elsewhere then? Constructors supporting old versions shouldn't be used other than
for reconstructing values from old facts. Or perhaps the value object itself shouldn't be concerned about enforcing which version can
be used? For instance, imagine that BusinessNumbers were initially just sequential integers and then they evolve into a format like [BusinessTypeAcronym]-[RegistrationYear]-[SequentialNumber]
you wouldn't want to allow new BusinessNumber(1234) into the domain. However, you cannot ignore the fact that some older business numbers have that format...

Peter Hageus

unread,
Aug 27, 2016, 6:10:05 AM8/27/16
to ddd...@googlegroups.com
Just had a very quick look at the sample code, and those value objects seems to be single property only. If you set up your serialiser to handle them properly, the result will be the same as using primitives. Basically a typedef for a language not supporting it. With a little discipline I don’t see any problem with this, and it makes for very readable and typeseafe code. A dynamic language consuming those events would not see any difference.

With more complex value objects I would agree to not put them in the events. (or at least define a dto-version of them)

/Peter

João Bragança

unread,
Aug 27, 2016, 6:21:36 PM8/27/16
to ddd...@googlegroups.com
why can't you have multiple constructors? How is stuff getting into your domain? There's no reason for new commands to send legacy numbers right? you can keep the old constructor around to construct this value object from events. Just write a test to make sure your command handlers don't accept the single number.

To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Alexandre Potvin Latreille

unread,
Aug 27, 2016, 9:44:17 PM8/27/16
to DDD/CQRS, joao...@braganca.name
I could do that, but wouldn't that be leaking business logic in command handlers? The domain should dictate which business number format is accepted and when, not command handlers or command themselves no?

ahjohannessen

unread,
Aug 28, 2016, 10:46:44 AM8/28/16
to DDD/CQRS
Sure, but I think that depends on what kind of value objects we talk about here. 
We have some value objects in protobuf, that are very stable and not really subject of versioning, 
on most of our events, e.g.:

// A 128-bit id, transferred as two 64-bit longs
message UUID {
  required sint64 least_significant_bits = 1;
  required sint64 most_significant_bits  = 2;
}

// Seconds from the epoch of 1970-01-01T00:00:00Z
message UnixTime {
  required int64 from_epoch = 1;
}

message Duration {
  required int32 seconds = 1;
}

message BusinessKey {
  required string id = 1;
}

message UnionKey {
  required int32 id = 1;
}

message BankAccountNumber {
  required int32 reg = 1;
  required int64 number = 2;
}

message Fraction {
  required int32 basis_point = 1;
}

message Money {
  required int32 pennies = 1;
}

message Ssn {
  required string id = 1;
}

message ClaimantKey {
  required string id = 1;
}

message ClaimKey {
  required ClaimantKey claimant_key = 1 [(scalapb.field).type = "toolbox.data.ClaimantKey"];
  required int64 id                 = 2;
}

message Day {
  required int32 year  = 1;
  required int32 month = 2;
  required int32 day   = 3;
}

message DayInterval {
  required Day from   = 1  [(scalapb.field).type = "toolbox.data.Day"];
  optional Day to_opt = 2  [(scalapb.field).type = "toolbox.data.Day"];
}

message DayRange {
  required Day from  = 1  [(scalapb.field).type = "toolbox.data.Day"];
  required Day to    = 2  [(scalapb.field).type = "toolbox.data.Day"];
}

message DaySet {
  repeated Day days  = 1  [(scalapb.field).type = "toolbox.data.Day"];
}

message PeriodKey {
  required int32 year   = 1;
  required int32 number = 2;
}

message Period {
  required PeriodKey period_key    = 1  [(scalapb.field).type = "toolbox.data.PeriodKey"];
  required DaySet    benefit_days  = 2  [(scalapb.field).type = "toolbox.data.DaySet"];
  required DayRange  range         = 3  [(scalapb.field).type = "toolbox.data.DayRange"];
}

enum Sign {
  Zero     = 0;
  Positive = 1;
  Negative = 2;
}

message DurationDelta {
  required Duration delta = 1  [(scalapb.field).type = "toolbox.data.Duration"];
  required Sign     sign  = 2  [(scalapb.field).type = "toolbox.data.Sign"];
}

message MoneyDelta {
  required Money delta = 1  [(scalapb.field).type = "toolbox.data.Money"];
  required Sign  sign  = 2  [(scalapb.field).type = "toolbox.data.Sign"];
}


After a year in production, none of those above have been subject for change.

Greg Young

unread,
Aug 28, 2016, 10:57:23 AM8/28/16
to ddd...@googlegroups.com
These are not domain objects.
Reply all
Reply to author
Forward
0 new messages