Neos.EventSourcing icon indicating copy to clipboard operation
Neos.EventSourcing copied to clipboard

Configurable Event mapping/upcasting

Open bwaidelich opened this issue 9 years ago • 5 comments

It should be possible to "hook into" the event handling process (during publishing & replay) in order to change the event class.

Example

Imagine you have an event CustomerWasRenamed() that expects the $fullName as constructor argument. Now you want to split that into $givenName and $familyName but there are already existing events in the store with the old format.

Solution 1 (custom TypeConverter)

You could implement a custom CustomerWasRenamedTypeConverter that accepts an array and supports both variants

Solution 2 (Accept both variants in the event implementation)

With the rename example that would be a bit hacky and could look sth like:

final class CustomerWasRenamed implements EventInterface
{
    public function __construct(string $fullName = null, string $givenName = null, string $familyName = null)
    {
        if ($fullName !== null) {
            list($this->givenName, $this->familyName) = explode(' ', $fullName);
        }
    }
}

But this is perfectly fine if you just add new parameters (which is probably the more common scenario)

Solution 3 (Create a new Event)

Obviously one can just create a new Event, i.e. CustomerWasRenamedV2 and deprecate the old one (again that doesn't really make sense for the given example, but usually the need for an upcast often comes from a different usecase that justifies a new event)

Solution 4 (Framework support)

We have a central authority for mapping event types in the form some.bounded.context:eventname to the event implementation. We could provide some simple mechanism to let 3rd party code hook into it in order to use a different implementation based on the payload

bwaidelich avatar Nov 07 '16 15:11 bwaidelich

I think the solution which is easiest to understand and to implement is no. 2. I guess no. 1 and no. 3 will always be (additional) options. I'm not sure about how 4 would look like in practice.

robertlemke avatar Nov 09 '16 09:11 robertlemke

But that means the event constructor will grow and grow with the earlier arguments always being null?

new CustomerWasRenamed(null, $foo, $bar) forever then? Sounds not cool.

kitsunet avatar Dec 15 '16 12:12 kitsunet

But that means the event constructor will grow and grow with the earlier arguments always being null?

No, why do you think that? That was just the case in my (oversimplified) example, but there are loads of cases where you can make changes in a B/C manner (as you know) ;) And if it gets cumbersome or dirty, you take another route.

Just to avoid confusion: The suggested solutions are not meant to be exclusive but we should add support for all of them so you can choose the right tool for the right situation.

bwaidelich avatar Dec 15 '16 13:12 bwaidelich

👍 I mean, in general it sounds good and I see it similar as Robert!

kitsunet avatar Dec 15 '16 16:12 kitsunet

In practice you can often get away with backwards-compatible changes to events, by adding or even removing properties, but not renaming them.

One feature we are currently missing is to simply ignore non-existent properties when mapping a recorded event to an event class. Staying with @bwaidelich's example, if your event contained a property "region" and you later decide to drop the property (without a replacement), you'd currently get a property mapping error when old events are mapped to the refactored event class:

Exception while property mapping for target type "…MyEvent", at property path "": Missing constructor argument "region" for object of type "…MyEvent".

We need to teach the ObjectConverter to acknowledge the shouldSkipUnknownProperties() flag in order to allow this.

robertlemke avatar Mar 08 '17 09:03 robertlemke