4

When building an event store, the typical approach is to serialize the event and then persist the type of the event, the body of the event (the serialized event itself), an identifier and the time it occurred.

When it comes to the event type, are there any best practises as to how these should be stored and referenced? Examples I see store the fully qualified path of the class ie.

com.company.project.package.XXXXEvent

What effort is then required though if you decide to refactor your project structure?

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Steven
  • 823
  • 8
  • 25
  • I tend to view the set of events as being relative to a given aggregate - thus the event type identifier string prefixing serves no purpose; [you're always decoding in the context of a given aggregate](https://eiriktsarpalis.wordpress.com/2018/10/30/a-contract-pattern-for-schemaless-datastores) – Ruben Bartelink Jul 06 '20 at 22:33

4 Answers4

8

After years running event-sourced applications in production, we avoid using fully qualified class names or any other platform-specific identifiers for event types.

An event type is just a string that should allow any kind of reader to understand how the event should be deserialized. You are also absolutely right about the issue with refactoring the application structure that might lead to changes in the class name.

Therefore, we use a pre-configured map that allows resolving the object type to a string and to reverse the string to an event type. By doing so, we detach the event type meta from the actual class and get the freedom to read and write events using different languages and stacks, also being able to freely move classes around of needed.

Alexey Zimarev
  • 17,944
  • 2
  • 55
  • 83
  • Thank you. Yes, that makes great sense and chimes with one of the approaches we were considering. I thought it interesting how many examples don't consider this aspect, but I guess this is more a tactical concern. – Steven Jul 10 '20 at 10:05
  • In terms of your pre-configured map, do you implement that as a shared kernel between all your bounded contexts? Or do you convert your domain events to integration events before publishing them as a message? (ie. the pre-configured map is only used within the bounded context) – Steven Jul 10 '20 at 10:45
  • Shared Kernel is a part of the domain model that is used in different contexts. Event type to string mapping is not a part of the domain, it's merely an infrastructural concern. It is only needed to make the serialisation work properly, so I'd never assumed it as something that concerns the domain. – Alexey Zimarev Jul 10 '20 at 14:11
4

What effort is then required though if you decide to refactor your project structure?

Not a lot of effort, but some discipline.

Events are messages, and long term viability of messages depend on having a schema, where the schema is deliberately designed to support forward and backward compatibility.

So something like "event type" would be a field name that can be any of an open set of identifiers which would each have an official spelling and semantics.

The spelling conventions that you use don't matter - you can use something that looks like a name in a hierarchical namespace, or you can use a URI, or even just a number like a surrogate key.

The identifiers, whatever convention you use, are coupled to the specification -- not to the class hierarchy that implements them.

In other words, there's no particular reason that org.example.events.Stopped necessarily implies the existence of a type org.example.events.Stopped.

Your "factories" are supposed to create instances of the correct classes/data structures from the messages, and while the naive mapping from schema identifier to class identifier works, then yes, they can take that shortcut. But when you decide to refactor your packages, you have to change the implementation of the factory such that the old identifiers from the message schema map to the new class implementations.

In other words, using something like Class.forName is a shortcut, which you abandon in favor of doing the translation explicitly when the short cut no longer works.

VoiceOfUnreason
  • 52,766
  • 5
  • 49
  • 91
  • I like the idea of a `uri` and I use those for my permissions. Never thought about having an abstract name for a domain event although I have been mapping them. I am stealing that! :P – Eben Roux Jul 07 '20 at 12:59
  • Thank you for your response. I think this is an equally good answer, so I awarded based on first to respond. Like the comment above, I am intrigued by the URI or surrogate key ideas. – Steven Jul 10 '20 at 10:42
3

Since event sourcing is about storing Domain Events, we prefer to avoid package-names or other technical properties in the events. Especially when it comes to naming them, since the name should be part of ubiquitous language. Domain Experts and other people don't lean on package names when making conversation about the domain. Package names are a language construct that also ties the storage of the Domain Events with the use of them within your software, which is another reason to avoid this solution.

We sometimes use the short class name (such as Class.forName in Java) to make mapping to code simpler and more automatic, but the class names should in that case be carefully chosen to match the ubiquitous language so that it still is not too implementation-specific.

Additionally, adding a prefix opens upp the possibility to have multiple Event Types with the same name but using different prefixes. Domain Events are part of the context of the Aggregate they are emitted from and therefore the Aggregate type can be useful to embed in the event. It will scope your events so you don't have to make up synthetic prefixes.

2

If you store events from multiple bounded contexts in one store, BoundedContext.EventThatHappened. Past tense for events, and event names are unique for a bounded context. As your events will change implementation, there is no direct connection to a class name.

Stephan Eggermont
  • 15,847
  • 1
  • 38
  • 65
  • 1
    that seems like a recipe for every event name needing to include the name of the Aggregate in order to disambiguate Event names that are not unique across the BC. I tend to view Event Types as 'owned' by the Aggregate, not the BC or broader system for this reason (it also makes it harder to move Aggregates across BC when your split was not perfect first time) – Ruben Bartelink Jul 08 '20 at 18:55
  • Yes, that is the point of a bounded context. Precision in the ubiquitous language, no aliased names. – Stephan Eggermont Jul 09 '20 at 13:57
  • I'm well aware of the point of a BC, UL and all the other buzzwords - but you're ignoring my point about the fact that this strategy is brittle. But I'll grant you having to ensure all event types are unique is definitely a way to keep the UL front of mind; I can imagine it teaching you a lot (but I don't feel it's practical - but that's just my opinion of course...). – Ruben Bartelink Jul 09 '20 at 21:02
  • I find the focus on the UL more important, indeed. You have the same brittleness when aggregates change – Stephan Eggermont Jul 10 '20 at 14:38
  • Wait, Aggregates change? :D Thanks for taking the time to do thoughtful followups, +1 – Ruben Bartelink Jul 10 '20 at 17:13