Event Sourcing from the Trenches: Domain Events
While visiting QCon New York this year, I realized that a lot of the architectural problems that were discussed there, could benefit from the Event Sourcing architecture style. Since I've been in charge of architecting such a system for several years now, I started to reflect on the work we've done, e.g. what worked for us, what would I do differently next time, and what is it that I still haven't made my mind up about. After having discussed aggregates in my last post, let me share some of my thoughts on domain events.
Avoid CRUD terminology
Don't use terms like create, update and delete in the names of your events. Business doesn't talk in those terms, so you should not either. Stick to the terminology from your Ubiquitous Language that applies to the involved bounded context. Again, Event Storming will help here as well since the names will surface during the discussion with business.
Prefer fine-grained events
Avoid coarse-grained events, even if the latter originated from an Event Storming session. High-level events might be more aligned with the business, but they also require a lot of context to understand. As an example, consider a risk assessment for some work to do. Only such an assessment involves a high-risk task, an entire team of specialists is needed. Now consider an assessment for a high-risk which risk level is reduced to low-risk. It basically means the assessment team needs to be disbanded. How would you model that? Have a high-level event like TaskRiskLevelReduced? But that means that all subscribers need to understand that this means that that assessment team is no longer necessary. I prefer to raise separate events, e.g. a TaskRiskLevelReduced first followed by a RiskAssessmentTeamDisbanded. If the rules changes in the future, you'll be more flexible.
Make event merging a first class concern
Since our aggregates never need to handle concurrent commands, we could get away with optimistic and pessimistic locking on the aggregate level. But after two years or so, we couldn't maintain this stance anymore. Adding event merging to your architecture afterwards can be challenging. So, please consider adding this to your ES implementation as soon as possible. I just wish there was a .NET open-source project that could do that.
Allow multi-event conversion
Events evolve, you can't avoid that. Sometimes a event receives an extra property that has a suitable default. Sometimes an event is renamed or converted in another event. But sometimes you need to convert a couple of events into a single new one, or the other way around. So if load your events from an event store, make sure your up converters can have state to support this. As far as I know, none of the .NET event store library have this out-of-the-box.
Use primitive types
In the DDD worlds, it's very common to build domain-specific value types to represent concepts such as ISBN numbers, phone numbers, zip codes, etc. Never ever put these on your events. Events represent something that has happened. Just imagine if somebody changes the constraints that apply to an ISBN number and the old event doesn't comply to it. Moreover, serializing and deserializing those types to the underlying store is usually more expensive than when you use simple primitive types.
Don't enrich events
Don't enrich events with information from the aggregate or its association just to make it available for projection purposes later on. It'll create unnecessary duplication of data and increases the change you'll need event versioning. The only exception I can think of is when that data is functional. E.g. when you add a product to an order and it's important to track the price at that point, you would probably make it part of the aggregate's events. In that case, you could handle all that in the projection code, but that means that that code needs to understand how prices are handled.
Events are your contract
Treat your events as the contract of your bounded context. Other bounded contexts or external systems can consume or subscribe to them to keep track of what's going on. It's the perfect integration technique for decoupling systems, in particular for building micro-services.
Don't use them as a notification mechanism within the bounded context
In the original definition of a domain event, events were used as a notification mechanism from one domain entity to another. So, if a product was discontinued, an event would be raised for that. A domain event handler somewhere else in the code base could handle that and perform a business action on all running orders that included that product. I used to love that mechanism, but found that it makes it particular difficult to trace what's going on in a system. Next to that, DDD introduced domain services for that. Notice the bounded context in this discussion. I'm only talking about the code within the bounded context.
Feedback, please!
So what do you think? Do my thoughts make sense? Am I too pragmatic here? Are you using Event Sourcing yourself? If so, care to share some experiences? Really love to hear your thoughts by commenting below. Oh, and follow me at @ddoomen to get regular updates on my everlasting quest for better solutions.
Leave a Comment