Basically, the question is :
how to correctly build an event storage for an event sourced system that should be able to :
convert an aggregate into another one,
keep the same Id,
and still be able to reconstitute it from the event stream?
Now my example:
i have a ProspectiveCustomer
that can be converted to PayingCustomer
like this :
ProspectiveCustomer::convertToPayingCustomer(ProspectiveCustomerId $id)
The PayingCustomer would keep the same Id so its lifetime can be followed up.
So now imagine the following event Stream :
Let's focus on point 4) :
We'll have a commandHandler that recieves a paymentCommand {customerId:"123", amount:"500€"}. Its job would be to :
My question is about 1) reconstituting from History :
The EventStorage service would :
The events'stack would now contain :
How could the commandHandler process PayingCustomer::reconstituteFromHistory(EventsHistory $events)
while the $events are events issued from / applyable to the ProspectiveCustomer
currently i'm handling the problem with PayingCustomer having its own Id, but holding a reference to the ProspectiveCustomerId.
But considering that :
it kind of feels messy because the model is now polluted by 2 Ids whereas one should be enough.
If it wouldn't be an event-sourced system, I'd definitly go for one unique Id.
That being said, and considering Event-sourcing is just an implementation detail, i'm looking for a way to have both aggregates keeping the same Id.
You have two aggregates but there is no "conversion". You step on a dangerous road, which can lead you to converting shopping carts to orders (for example).
You already have two different concepts - prospective customer and paying customer. They were probably identified during your conversations with domain experts. This clearly means two aggregates, sometimes two bounded contexts. You should not do any conversion, but you can definitely create new aggregate reacting on something that happens in your system (order accepted).
I would also expect that the term "conversion" came to you from domain experts. This is normal, since in Sales they use this terminology to indicate that someone who was interested actually made a purchase. They indeed call it "conversion" and you would be right including it to your ubiquitous language by using "3. Prospective customer converted" but this has nothing to do with technical conversion, meaning changing the object type.
You need domain event handlers that would do (3) and (4) since you're saying this is the same bounded context.
Aggregate unique identity generation is not a function of the aggregate itself, it is done outside and the aggregates gets its identity when being created as a factory method or/and constructor parameter. Therefore, when you create paying customer from a prospective customer, nothing stops you from using the same identity.
However, you start to have assumptions that you always expect to have a prospective customer in order to retrieve your history or something else, using the same identity. Since this assumption is implicit, it is easily forgotten and in general especially discouraged in DDD (remember making implicit things explicit). You can easily keep the id reference to the prospective customer in your new paying customer and then you will be perfectly fine.