In this first step implementing event-driven integration between services, we’ll hook-up into EF Core’s infrastructure, namely when saving changes, to infer if any event should be raised based on the information provided by the change tracker.
Note: depending on your preference, you can check out the following video, otherwise, skip to the written version below.
The playlist for the whole series is here.
As introduced in the previous episode, we’re going to implement the transactional outbox pattern, to make event delivery reliable. In this first step, we’re going to look at where we can put the event creation and storage logic.
As it’s made obvious from the post title, we’re going to hook-up into EF Core’s infrastructure to implement this, but before, we’ll take a look at a couple of alternative options.
Where to put the event creation and storage logic
Let’s begin with a small task: where do we put the event creation and storage logic?
Normally I’d say together with the specific domain logic. Thinking of the register user case, this means either in the
PlayBallUser entity itself or in
UserManager.CreateAsync. Problem is, as this is ASP.NET Core Identity code, we don’t have full control to put this extra logic in there (though we could probably do something about it, considering
UserManager has its public members marked as
So, as this approach is not easily doable, how about some other options?
As we discussed in the previous episode, what we require is that things are done in a single transaction, so we could to go
Register.cshtml.cs and do something like (pseudo-code):
This would do the trick, but it doesn’t feel like the right place to put this kind of code, feels like it’s mixing things up, considering we’re in the page, which should be more worried with invoking domain logic and mapping that to the UI, not dealing with transactions.
The last option we’ll look at, and the one we’ll implement, is to take advantage of EF Core’s extensibility points, namely by overriding
SaveChanges and using the change tracker to get information about what happened.
Overriding EF Core SaveChanges
Overriding EF Core
SaveChanges is a common strategy, as it allows centralization of certain types of logic that would be a pain to have spread everywhere.
Some common use-cases are related to events, like is our case, but not necessarily inferring them as we’ll do. As an example from Steve Smith’s Clean Architecture template, he overrides
SaveChanges to get the domain events added to the entities, then dispatches them.
Another common use-case is to handle properties we’d like to be filled in automatically, like entity modification date and author. The linked post implements something like this.
The gist of the approach is rather simple: in
SaveChanges and add the extra code.
Note: in this implementation we’re overriding one of
SaveChangesAsync overloads only, and it’s enough because it’s the commonly used one, but to be on the safe side, we should override all overloads of
Inferring events from the change tracker
DbContext class exposes a property
ChangeTracker, where we can access all the entities that have been changed in some way, requiring them to be persisted. We’ll take advantage of this to find all changes to entities of type
PlayBallUser, then map them to events (in the next post).
As a quick example, if we want to get all created users - normally it will be a single one, but we can make the code generic - we can do something like the following:
To avoid putting too much responsibilities into our
AuthDbContext, instead of having it all directly in the
SaveChangesAsync implementation, we can extract things. With this in mind we can create an
IEventDetector interface, which can be implemented to detect different kinds of events.
Now in the
AuthDbContext, we get instances of
IEventDetector provided through the constructor and invoke them in
With this we avoid bloating
AuthDbContext, keeping all event detection segregated. As we have three kinds of events (user registered, updated and deleted), we create three implementations of
User registered and deleted are the most straightforward, as we need only to check if the entity state is
Detecting user updated events is slightly more complicated, just because we don’t necessarily want to create an event for every type of change. For instance, it’s not really relevant for the other services to get an event when the password changes, or two-factor is enabled, as these are more internal responsibilities of the auth service, not really something other services care about (depends on your use case of course).
With this in mind, for user updated event detection, besides checking that the entity state is
Modified, we also check if any of the properties that are relevant for other services have changed. In this case let’s assume the only thing other services care about is the
UserName property. To do this check we can use the
And with this we have things in place to detect the three types of events we’ll be publishing.
Just as a side note, to get the
IEventDetectors configured in DI in order to be injected into the
AuthDbContext, I’m using Scrutor:
That does it for this episode. We took a quick look at hooking into EF Core’s infrastructure, overriding
SaveChanges and inferring events from the change tracker.
Main takeaways are:
SaveChangesis a common strategy for centralizing code that acts on entities just before/after persistence changes
- we have access to Entity Framework’s change tracker, being able to see what changes were applied to our entities
In the next episode we’ll build upon these event detectors, mapping the detected changes to actual events we’ll store in the outbox table.
As a quick PSA before closing, just to remind that many of the problems we’re looking into in these event-driven topic (not this episode in particular) can solved by existing libraries, so it might be interesting to look into them before doing everything manually. We’re doing things manually in the series to make the problems surface so everyone’s aware o them, not just assume everything works magically. As a couple of examples of such libraries for .NET projects, we have MassTransit and NServiceBus.
Links in the post:
The source code for this post is in the Auth repository, tagged as
Sharing and feedback always appreciated!
Thanks for stopping by, cyaz!