Deserialisation in C#

I have a json string which I want to deserialise into a C#.
Our current process is preserving the $type in each event:

For example:

Vme.Eposity.SalesTransactions.DomainEvents.SalesTransactionCompletedEvent, Vme.Eposity.SalesTransactions.DomainEvents

We are exploring ways of allowing us to remove this dependency, and use “SalesTransactionCompletedEvent” from the EventType.

The problem is, we are struggling to come up with a clean way of managing the deserialisation (at the moment, the only idea we have is a Dictionary with EventType as the key, and C# Type as value.

Anyone got any tips on this?

the only idea we have is a Dictionary with EventType as the key, and C# Type as value.

What’s wrong with this? I’ve done it this way. Also, I would have a dictionary go the other way, when you need to serialize.

Just remembering to manually add this registration each time we introduce a new event.
So in our current $type approach, we simply do this on the aggregate:

public void PlayEvent(TheNewEvent @event) and we don’t need to wire anything up because our base class does this:

PlayEvent( (dynamic)@event ) //calls the correct version

In the lookup scenario, I have to create the method and also add a registration manually for this new event.

Why not use reflection to discover all the events?

@steven.blair if you don’t mind reflection then I have a sample (for Kafka Consumer, but it’s the same mechanism).

  1. Send only the event type name.
  2. Get it from the $type value https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Core.Streaming.Kafka/Consumers/KafkaConsumer.cs#L75
  3. Find the correct type: https://github.com/oskardudycz/EventSourcing.NetCore/blob/af92a94320cc64808a0a790594224c4b52311b96/Core/Reflection/TypeProvider.cs#L9
  4. Run handler: https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Core.Streaming.Kafka/Consumers/KafkaConsumer.cs#L86

The only con is if you have more than one event with the same name but different namespace (however, you can probably enhance that).

I’d suggest to not use the full type name, as it’s hard to maintain in the long term. I also think that having some Dictionary with mapping would be more explicit and easier to track.

Let me try this out.
It may well be I have to live with the wiring up.

Thanks for the help.

@steven.blair it would be great if you could send feedback if that worked out. If not we may try to come up with a different idea.

Oskar,

That code works, but now I have pieced it together, I realise I don’t want to deserialise :frowning:
The change I am playing around with is to make my domain event base class hold a JObject, and the child class simply does something like this:

public class ProductEvent : DomainEvent{
public Guid ProductId => this.JsonObject[“productId”].ToObject();
}

Could you explain why you don’t want to deserialise? I think that Domain events should be, in general, serialisation agnostic. What benefit do you see in using the approach you posted?

Oskar,

I was reading Greg Young’s “Versioning in an Event Sourced System” book and this is one of the suggestions he makes on how to build domain events. Making schema changes might be easier going forward rather than attempting to deserialise to something changes over time.

Making the domain events work this way is fairly trivial, but the problem is getting my resolved events json into this format without manually wiring it up.

So oskar, you think the domain event should know nothing about what it will eventually become?

I’m not dogmatic on keeping the domain events clean. I agree that we should select the pragmatic way. If that works for you, then, of course, it’s okay.

I prefer to create a new class using the “Upcaster” pattern regarding the technical details. Such a pattern is a middleware, that takes JsonObject and maps to the current class structure. Then you don’t have to put serialisation details in your event class and make it more explicit. It also helps later as after a few renames you may end up with different ifs and conditions from which particular property to get data.

My take on that is that if we have to do that too often, it is a sign for me to work more on strategy. Using such an approach can blurry the real business change that we did. E.g. reshaping the object structure comes with some business requirement. Some field rename may also be the sign of changed business logic.

In general, I think that the best is to keep the streams/aggregates short-living then you may not need to care much about versioning. I wrote more about that in my blog post: https://event-driven.io/en/how_to_do_event_versioning/. Savvas also wrote about that in https://www.eventstore.com/blog/event-immutability-and-dealing-with-change.

Of course, if we’re aiming to fix a typo, etc., then it may work.
Also, the question is if those events will be only used inside the module? If yes, that will work, but if those events are also published to other modules, they also need to know about it. Greg also wrote a few possible scenarios explaining that it gets way trickier (e.g. publishing both old and new event format).

Oskar,

Previously we would have consumers who would need access to the domain events, but now (for the most part anyway) we create “enriched” events which our consumers pick rather than the original domain events.
This has freed us up to explore alterative ways of persisting our domain events.
The current approach where we have $type helps our rehydration, but also makes it make fairly straight forward for consumers to deserialise.

I am going to keep playing around with the code for now. I hear what you are saying regarding JObject though, and that’s something I might reconsider.

Just for reference, my Rehydration method (Load) currently looks like this using your advice:

public T Load(Guid aggregateId)
{
    //This would be our resolvedEvents
    List<(String eventType, String body)> resolvedEvents = this.GetEvents(aggregateId);

    List<DomainEvent> domainEvents = new List<DomainEvent>();

    foreach (var resolvedEvent in resolvedEvents)
    {
        var eventType = TypeProvider.GetTypeFromAnyReferencingAssembly(resolvedEvent.eventType);
        var domainEvent = JsonConvert.DeserializeObject(resolvedEvent.body, eventType);

        domainEvents.Add(  (DomainEvent)domainEvent);
    }

    T aggregate = domainEvents.Aggregate(new T
                                         {
                                             AggregateId = aggregateId
                                         },
                                         (aggregate1,
                                          @event) =>
                                         {
                                             aggregate1.Play(@event);
                                             return aggregate1;
                                         });

    return aggregate;
}

Yeah, that’s great that you already separated the internal and external events. In one of my previous projects, we were not great on that, which got us into real trouble (ended-up as distributed monolith…). It would also be useful to consider the strategy when enriching the public event structure changed. It’s not needed to apply that in advance, but having some strategy upfront should help you in the future.

The logic you posted looks good.

Regarding upcasting idea, this could be something like that. Having some upcasters cache:

private readonly Dictionary<Type, Func<JObject, DomainEvent>> Upcasters;

You could do upcasting when it’s needed and get clean events.

var eventType = TypeProvider.GetTypeFromAnyReferencingAssembly(resolvedEvent.eventType);
var domainEvent = Upcasters.TryGetValue(eventType, out var upcaster) ?
    upcaster(JObject.Parse(resolvedEvent.body)) 
    : (DomainEvent)JsonConvert.DeserializeObject(resolvedEvent.body, eventType);

domainEvents.Add(domainEvent);

You could also make upcasting more generic by adding the aggregate/projection type (projections, depending on the approach, may also need upcasting).

Oskar,

Thanks a lot for this today.
Btw, when you show an Upcaster, is that essentially the same as injecting a factory into the repository (which I do):

public interface IDomainEventFactory
{
DomainEvent CreateDomainEvent(ResolvedEvent @event);
}

The concrete gets a json serialiser injected in:

public DomainEventFactory() => this.Serialiser = JsonSerialiser.EventStoreDefault();

1 Like

Yes, with the difference is that I’m passing JObject (as you had in the initial sample) :ok_hand:

Feel free to send more questions/feedback when you encounter something new on the way :slight_smile:

Thanks.
I think I have enough to get past my original problem now.

1 Like

here’s one way we tackled that
https://github.com/ReactiveDomain/reactive-domain/blob/master/src/ReactiveDomain.Foundation/StreamStore/JsonMessageSerializer.cs
and the interface
https://github.com/ReactiveDomain/reactive-domain/blob/4a0e1fb92b02414e7acbcd0bf073c56453254979/src/ReactiveDomain.Foundation/StreamStore/IEventSerializer.cs

and usage
https://github.com/ReactiveDomain/reactive-domain/blob/4a0e1fb92b02414e7acbcd0bf073c56453254979/src/ReactiveDomain.Foundation/StreamStore/StreamStoreRepository.cs

Chris,

Thanks. I will have a look over this.