Serialization
As described on the Domain events page, events must be (de)serializable. Eventuous doesn’t care about the serialization format, but requires you to provide a serializer instance, which implements the IEventSerializer interface.
The serializer interface is simple:
public interface IEventSerializer { DeserializationResult DeserializeEvent(ReadOnlySpan<byte> data, string eventType, string contentType);
SerializationResult SerializeEvent(object evt);}The serialization result contains not only the serialized object as bytes, but also the event type as string (see below), and the content type:
public record SerializationResult(string EventType, string ContentType, byte[] Payload);Type map
Section titled “Type map”For deserialization, the serializer will get the binary payload and the event type as string. Event store is unaware of your event types, it just stores the payload in a binary format to the database, along with the event type as string. It is up to you how your strong event types map to the event type string.
Therefore, we need to have a way to map strong types of the events to strings, which are used to identify those types in the database and for serialization. For that purpose, Eventuous uses the TypeMap. It is a singleton, which is available globally. When you add new events to your domain model, remember to also add a mapping for those events. The mapping is static, so you can implement it anywhere in the application. The only requirement is that the mapping code must execute when the application starts.
For example, if you have a place where domain events are defined, you can put the mapping code there, as a static member:
static class BookingEvents { // events are defined here
public static void MapBookingEvents() { TypeMap.AddType<RoomBooked>("RoomBooked"); TypeMap.AddType<BookingPaid>("BookingPaid"); TypeMap.AddType<BookingCancelled>("BookingCancelled"); TypeMap.AddType<BookingImported>("BookingImported"); }}Then, you can call this code in your bootstrap code:
BookingEvents.MapBookingEvents();Auto-registration with source generator
Section titled “Auto-registration with source generator”The recommended way to register event types is to use the [EventType] attribute combined with the Eventuous source generator. The generator automatically discovers all types decorated with [EventType] in your project and generates a module initializer that registers them at startup — no manual registration code needed.
Annotate your events with the [EventType] attribute:
[EventType("V1.FullyPaid")]public record BookingFullyPaid(string BookingId, DateTimeOffset FullyPaidAt);
[EventType("V1.RoomBooked")]public record RoomBooked(string RoomId, LocalDate CheckIn, LocalDate CheckOut, float Price);That’s it. The source generator produces a module initializer class per assembly, which calls TypeMap.Instance.AddType(...) for each annotated event type. Registration happens automatically when the assembly is loaded — you don’t need to write any startup code.
Reflection-based registration
Section titled “Reflection-based registration”As an alternative to the source generator, you can use reflection-based registration. This scans assemblies at runtime for types decorated with [EventType]:
TypeMap.RegisterKnownEventTypes();The registration won’t work if event classes are defined in another assembly, which hasn’t been loaded yet. You can work around this limitation by specifying one or more assemblies explicitly:
TypeMap.RegisterKnownEventTypes(typeof(BookingFullyPaid).Assembly);Default serializer
Section titled “Default serializer”Eventuous provides two serializers based on System.Text.Json. Configure one before constructing event stores, producers, or subscriptions that use it. In 0.17, there is no automatically created event serializer: accessing EventSerializer.Default before configuration throws InvalidOperationException.
Source-generated JSON
Section titled “Source-generated JSON”For trimmed or Native AOT applications, use DefaultStaticEventSerializer from Eventuous.Serialization. Define a JsonSerializerContext containing every event type that your application serializes:
using System.Text.Json;using System.Text.Json.Serialization;
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web)][JsonSerializable(typeof(RoomBooked))][JsonSerializable(typeof(BookingPaid))]public partial class EventJsonContext : JsonSerializerContext { }var serializer = new DefaultStaticEventSerializer(EventJsonContext.Default);EventSerializer.SetDefault(serializer);builder.Services.AddSingleton<IEventSerializer>(serializer);JSON source generation supplies serialization metadata; event names still need registration in TypeMap, either through the Eventuous source generator or explicitly.
Reflection-based JSON
Section titled “Reflection-based JSON”For reflection-based serialization, add a reference to Eventuous.Serialization.Json.Dynamic. The DefaultEventSerializer class remains in the Eventuous namespace:
dotnet add package Eventuous.Serialization.Json.Dynamicvar serializer = new DefaultEventSerializer( new JsonSerializerOptions(JsonSerializerDefaults.Web));EventSerializer.SetDefault(serializer);builder.Services.AddSingleton<IEventSerializer>(serializer);Preserve any JSON options and converters your stored events require. Constructing DefaultEventSerializer sets the global default only if one has not already been configured; an explicit EventSerializer.SetDefault makes startup configuration clear and can replace a previous default. Its constructor reports trimming and AOT warnings because this implementation uses reflection.
Custom serializers and dependency injection
Section titled “Custom serializers and dependency injection”Register a custom IEventSerializer with DI to supply it to components resolved through the container. Use EventSerializer.SetDefault(serializer) as well when code constructs components directly and relies on their optional serializer argument. DI registration alone does not configure the global default for an arbitrary custom serializer.
Code that previously used DefaultEventSerializer.Instance or DefaultEventSerializer.SetDefaultSerializer(...) must use EventSerializer.Default or EventSerializer.SetDefault(...). See the 0.17 migration guide.
Metadata serializer
Section titled “Metadata serializer”In many cases you might want to store event metadata in addition to the event payload. Normally, you’d use the same way to serialize both the event payload and its metadata, but it’s not always the case. For example, you might store your events in Protobuf, but keep metadata as JSON.
Eventuous only uses the metadata serializer when the event store implementation, or a producer can store metadata as a byte array. For example, KurrentDB supports that, but Google PubSub doesn’t. Therefore, the event store and producer that use KurrentDB will use the metadata serializer, but the Google PubSub producer will add metadata to events as headers, and won’t use the metadata serializer.
For the metadata serializer the same principles apply as for the event serializer. Eventuous has a separate interface IMetadataSerializer, which has a default instance created on startup by implicitly. You can register a custom metadata serializer as a singleton or override the default one by calling DefaultMetadataSerializer.SetDefaultSerializer function.