
I was helping a new JasperFx Software client this week to best integrate a Domain Events strategy into their new Wolverine codebase. This client wanted to use the common model of using an EF Core DbContext to harvest domain events raised by different entities and relay those to Wolverine messaging with proper Wolverine transactional outbox support for system durability. As part of that assistance — and also to have some content for other Wolverine users trying the same thing later — I promised to write a blog post showing how I’d do this kind of integration myself with Wolverine and EF Core or at least consider a few options. To try to more permanently head this usage problem for other users, I went into mad scientist mode this evening and just rolled out a new Wolverine 5.6 with some important improvements to make this Domain Events pattern much easier to use in combination with EF Core.
Let’s start with some context about the general kind of approach I’m referring to with…
Typical .NET Approach with EF Core and MediatR
I’m largely basing all the samples in this post on Camron Frenzel’s Simple Domain Events with EFCore and MediatR. In his example there was a domain entity like this:
// Base class that establishes the pattern for publishing
// domain events within an entity
public abstract class Entity : IEntity
{
[NotMapped]
private readonly ConcurrentQueue<IDomainEvent> _domainEvents = new ConcurrentQueue<IDomainEvent>();
[NotMapped]
public IProducerConsumerCollection<IDomainEvent> DomainEvents => _domainEvents;
protected void PublishEvent(IDomainEvent @event)
{
_domainEvents.Enqueue(@event);
}
protected Guid NewIdGuid()
{
return MassTransit.NewId.NextGuid();
}
}
public class BacklogItem : Entity
{
public Guid Id { get; private set; }
[MaxLength(255)]
public string Description { get; private set; }
public virtual Sprint Sprint { get; private set; }
public DateTime CreatedAtUtc { get; private set; } = DateTime.UtcNow;
private BacklogItem() { }
public BacklogItem(string desc)
{
this.Id = NewIdGuid();
this.Description = desc;
}
public void CommitTo(Sprint s)
{
this.Sprint = s;
this.PublishEvent(new BacklogItemCommitted(this, s));
}
}
Note the CommitTo() method that publishes a BacklogItemCommitted event that in his sample is published via MediatR with some customization of an EF Core DbContext like this from the referenced post with some comments that I added:
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
await _preSaveChanges();
var res = await base.SaveChangesAsync(cancellationToken);
return res;
}
private async Task _preSaveChanges()
{
await _dispatchDomainEvents();
}
private async Task _dispatchDomainEvents()
{
// Find any entity objects that were changed in any way
// by the current DbContext, and relay them to MediatR
var domainEventEntities = ChangeTracker.Entries<IEntity>()
.Select(po => po.Entity)
.Where(po => po.DomainEvents.Any())
.ToArray();
foreach (var entity in domainEventEntities)
{
// _dispatcher was an abstraction in his post
// that was a light wrapper around MediatR
IDomainEvent dev;
while (entity.DomainEvents.TryTake(out dev))
await _dispatcher.Dispatch(dev);
}
}
The goal of this approach is to make DDD style entity types the entry point and governing “decider” of all business behavior and workflow and give these domain model types a way to publish event messages to the rest of the system for side effects in the system outside of the state of the entity. Like for example, maybe the backlog system has to publish a message to a Slack room about the back log item being added to the sprint. You sure as hell don’t want your domain entity to have to know about the infrastructure you use to talk to Slack or web services or whatever.
Mechanically, I’ve seen this typically done with some kind of Entity base class that either exposes a collection of published domain events like the sample above, or puts some kind of interface like this directly into the Entity objects:
// Just assume that this little abstraction
// eventually relays the event messages to Wolverine
// or whatever messaging tool you're using
public interface IEventPublisher
{
void Publish<T>(T @event);
}
// Using a Nullo just so you don't have potential
// NullReferenceExceptions
public class NulloEventPublisher : IEventPublisher
{
public void Publish<T>(T @event)
{
// Do nothing.
}
}
public abstract class Entity
{
public IEventPublisher Publisher { get; set; } = new NulloEventPublisher();
}
public class BacklogItem : Entity
{
public Guid Id { get; private set; } = Guid.CreateVersion7();
public string Description { get; private set; }
// ZOMG, I forgot how annoying ORMs are. Use a document database
// and stop worrying about making things virtual just for lazy loading
public virtual Sprint Sprint { get; private set; }
public void CommitTo(Sprint sprint)
{
Sprint = sprint;
Publisher.Publish(new BackLotItemCommitted(Id, sprint.Id));
}
}
In the approach of using the abstraction directly inside of your entity classes, you incur the extra overhead of connecting the Entity objects loaded out of EF Core with the implementation of your IEventPublisher interface at runtime. I’ll do a few thought experiments later in this post and try out a couple different alternatives.
Before going back to EF Core integration ideas, let me deviate into…
Idiomatic Critter Stack Usage
Forget EF Core for a second, let’s examine a possible usage with the full “Critter Stack” and use Marten for Event Sourcing instead. In this case, a command handler to add a backlog item to a sprint could look something like this (folks, I didn’t spend much time thinking about how a back log system would be built here):
public record BackLotItemCommitted(Guid SprintId);
public record CommitToSprint(Guid BacklogItemId, Guid SprintId);
// This is utilizing Wolverine's "Aggregate Handler Workflow"
// which is the Critter Stack's flavor of the "Decider" pattern
public static class CommitToSprintHandler
{
public static Events Handle(
// The actual command
CommitToSprint command,
// Current state of the back log item,
// and we may decide to make the commitment here
[WriteAggregate] BacklogItem item,
// Assuming that Sprint is event sourced,
// this is just a read only view of that stream
[ReadAggregate] Sprint sprint)
{
// Use the item & sprint to "decide" if
// the system can proceed with the commitment
return [new BackLotItemCommitted(command.SprintId)];
}
}
In the code above we’re appending the BackLotItemCommitted event to Marten that’s returned from the method. If you need to carry out side effects outside of the scope of this handler using that event as a message input, you have a couple options to have Wolverine relay that through any of its messaging through the event forwarding (faster, but un-ordered) or event subscriptions (strictly ordered, but that always means slower).
I should also say that if the events returned from the function above are also being forwarded as messages and not just being appended to the Marten event store, that messaging is completely integrated with Wolverine’s transactional outbox support. That’s a key differentiation all by itself from a similar MediatR based approach that doesn’t come with outbox support.
That’s it, that’s the whole handler, but here are some things I would want you to take away from that code sample above:
- Yes, the business logic is embedded directly in the handler method instead of being buried in the
BacklogItemorSprintaggregates. We are very purposely going down a Functional Programming (adjacent? curious?) approach where the logic is primarily in pure “Decider” functions - I think the code above clearly shows the relationship between the system input (the
CommitToSprintcommand message) and the potential side effects and changes in state of the system. This relative ease of reasoning about the code is of the utmost importance for system maintainability. We can look at the handler code and know that executing that message will potentially lead to events or event messages being published. I’m going to hit this point again from some of the other potential approaches because I think this is a vital point. - Testability of the business logic is easy with the pure function approach
- There are no marker interfaces,
Entitybase classes, or jumping through layers. There’s no repository or factory - Yes, there is absolutely a little bit of “magic” up above, but you can get Wolverine to show you the exact generated code around your handler to explain what it’s doing
So enough of that, let’s start with some possible alternatives for Wolverine integration of domain events from domain entity objects with EF Core.
Relay Events from Your Entity Subclass to Wolverine
Switching back to EF Core integration, let’s look at a possible approach to teach Wolverine how to scrape domain events for publishing from your own custom Event or IEvent layer supertype like this one that we’ll put behind our BackLogItem type:
// Of course, if you're into DDD, you'll probably
// use many more marker interfaces than I do here,
// but you do you and I'll do me in throwaway sample code
public abstract class Entity
{
public List<object> Events { get; } = new();
public void Publish(object @event)
{
Events.Add(@event);
}
}
public class BacklogItem : Entity
{
public Guid Id { get; private set; }
public string Description { get; private set; }
public virtual Sprint Sprint { get; private set; }
public DateTime CreatedAtUtc { get; private set; } = DateTime.UtcNow;
public void CommitTo(Sprint sprint)
{
Sprint = sprint;
Publish(new BackLotItemCommitted(Id, sprint.Id));
}
}
Let’s utilize this a little bit within a Wolverine handler, first with explicit code:
public static class CommitToSprintHandler
{
public static async Task HandleAsync(
CommitToSprint command,
ItemsDbContext dbContext)
{
var item = await dbContext.BacklogItems.FindAsync(command.SprintId);
var sprint = await dbContext.Sprints.FindAsync(command.SprintId);
// This method would cause an event to be published within
// the BacklogItem object here that we need to gather up and
// relay to Wolverine later
item.CommitTo(sprint);
// Wolverine's transactional middleware handles
// everything around SaveChangesAsync() and transactions
}
}
Or a little bit cleaner with some Wolverine “magic” with Wolverine’s declarative persistence support if you’re so inclined:
public static class CommitToSprintHandler
{
public static IStorageAction<BacklogItem> Handle(
CommitToSprint command,
// There's a naming convention here about how
// Wolverine "knows" the id for the BacklogItem
// from the incoming command
[Entity] BacklogItem item,
[Entity] Sprint sprint
)
{
// This method would cause an event to be published within
// the BacklogItem object here that we need to gather up and
// relay to Wolverine later
item.CommitTo(sprint);
// This is necessary to "tell" Wolverine to put transactional middleware around the handler
// Just taking in the right DbContext type as a dependency
// work work just as well if you don't like the Wolverine
// magic
return Storage.Update(item);
}
}
Now, let’s add some Wolverine configuration to just make this pattern work:
builder.Host.UseWolverine(opts =>
{
// Setting up Sql Server-backed message storage
// This requires a reference to Wolverine.SqlServer
opts.PersistMessagesWithSqlServer(connectionString, "wolverine");
// Set up Entity Framework Core as the support
// for Wolverine's transactional middleware
opts.UseEntityFrameworkCoreTransactions();
// THIS IS A NEW API IN Wolverine 5.6!
opts.PublishDomainEventsFromEntityFrameworkCore<Entity>(x => x.Events);
// Enrolling all local queues into the
// durable inbox/outbox processing
opts.Policies.UseDurableLocalQueues();
});
In the Wolverine configuration above, the EF Core transactional middleware now “knows” how to scrape out possible domain events from the active DbContext.ChangeTracker and publish them through Wolverine. Moreover, the EF Core transactional middleware is doing all the operation ordering for you so that the events are enqueued as outgoing messages as part of the transaction and potentially persisted to the transactional inbox or outbox (depending on configuration) before the transaction is committed.
To make this as clear as possible, this approach is completely reliant on the EF Core transactional middleware.
Oh, and also note that this domain event “scraping” is also supported and tested with the IDbContextOutbox<T> service if you want to use this in application code outside of Wolverine message handlers or HTTP endpoints.
This approach could also support the thread safe approach that the sample from the first section used in the future, but I’m dubious that that’s necessary.
If I were building a system that embeds domain event publishing directly in domain model entity classes, I would prefer this approach. But, let’s talk about another option that will not require any changes to Wolverine…
Relay Events from Entity to Wolverine Cascading Messages
In this approach, which I’m granting that some people won’t like at all, we’ll simply pipe the event messages from the domain entity right to Wolverine and utilize Wolverine’s cascading message feature.
This time I’m going to change the BacklogItem entity class to something like this:
public class BacklogItem
{
public Guid Id { get; private set; }
public string Description { get; private set; }
public virtual Sprint Sprint { get; private set; }
public DateTime CreatedAtUtc { get; private set; } = DateTime.UtcNow;
// The exact return type isn't hugely important here
public object[] CommitTo(Sprint sprint)
{
Sprint = sprint;
return [new BackLotItemCommitted(Id, sprint.Id)];
}
}
With the handler signature:
public static class CommitToSprintHandler
{
public static object[] Handle(
CommitToSprint command,
// There's a naming convention here about how
// Wolverine "knows" the id for the BacklogItem
// from the incoming command
[Entity] BacklogItem item,
[Entity] Sprint sprint
)
{
return item.CommitTo(sprint);
}
}
The approach above let’s you make the handler be a single pure function which is always great for unit testing, eliminates the need to do any customization of the DbContext type, makes it unnecessary to bother with any kind of IEventPublisher interface, and let’s you keep the logic for what event messages should be raised completely in your domain model entity types.
I’d also argue that this approach makes it more clear to later developers that “hey, additional messages may be published as part of handling the CommitToSprint command” and I think that’s invaluable. I’ll harp on this more later, but I think the traditional, MediatR-flavored approach to domain events from the first example at the top makes application code harder to reason about and therefore more buggy over time.
Embedding IEventPublisher into the Entities
Lastly, let’s move to what I think is my least favorite approach that I will from this moment be recommending against for any JasperFx clients but is now completely supported by Wolverine 5.6+! Let’s use an IEventPublisher interface like this:
// Just assume that this little abstraction
// eventually relays the event messages to Wolverine
// or whatever messaging tool you're using
public interface IEventPublisher
{
void Publish<T>(T @event) where T : IDomainEvent;
}
// Using a Nullo just so you don't have potential
// NullReferenceExceptions
public class NulloEventPublisher : IEventPublisher
{
public void Publish<T>(T @event) where T : IDomainEvent
{
// Do nothing.
}
}
public abstract class Entity
{
public IEventPublisher Publisher { get; set; } = new NulloEventPublisher();
}
public class BacklogItem : Entity
{
public Guid Id { get; private set; } = Guid.CreateVersion7();
public string Description { get; private set; }
// ZOMG, I forgot how annoying ORMs are. Use a document database
// and stop worrying about making things virtual just for lazy loading
public virtual Sprint Sprint { get; private set; }
public void CommitTo(Sprint sprint)
{
Sprint = sprint;
Publisher.Publish(new BackLotItemCommitted(Id, sprint.Id));
}
}
Now, on to a Wolverine implementation for this pattern. You’ll need to do just a couple things. First, add this line of configuration to Wolverine, and note there are no generic arguments here:
// This will set you up to scrape out domain events in the
// EF Core transactional middleware using a special service
// I'm just about to explain
opts.PublishDomainEventsFromEntityFrameworkCore();
Now, build a real implementation of that IEventPublisher interface above:
public class EventPublisher(OutgoingDomainEvents Events) : IEventPublisher
{
public void Publish<T>(T e) where T : IDomainEvent
{
Events.Add(e);
}
}
OutgoingDomainEvents is a service from the WolverineFx.EntityFrameworkCore Nuget that is registered as Scoped by the usage of the EF Core transactional middleware. Next, register your custom IEventPublisher with the Scoped lifecycle:
opts.Services.AddScoped<IEventPublisher, EventPublisher>();
How you wire up IEventPublisher to your domain entities getting loaded out of the your EF Core DbContext? Frankly, I don’t want to know. Maybe a repository abstraction around your DbContext types? Dunno. I hate that kind of thing in code, but I perfectly trust *you* to do that and to not make me see that code.
What’s important is that within a message handler or HTTP endpoint, if you resolve the IEventPublisher through DI and use the EF Core transactional middleware, the domain events published to that interface will be piped correctly into Wolverine’s active messaging context.
Likewise, if you are using IDbContextOutbox<T>, the domain events published to IEventPublisher will be correctly piped to Wolverine if you:
- Pull both
IEventPublisherandIDbContextOutbox<T>from the same scoped service provider (nested container in Lamar / StructureMap parlance) - Call
IDbContextOutbox<T>.SaveChangesAndFlushMessagesAsync()
So, we’re going to have to do some sleight of hand to keep your domain entities synchronous
Last note, in unit testing you might use a stand in “Spy” like this:
public class RecordingEventPublisher : OutgoingMessages, IEventPublisher
{
public void Publish<T>(T @event)
{
Add(@event);
}
}
Summary
I have always hated this Domain Events pattern and much prefer the full “Critter Stack” approach with the Decider pattern and event sourcing. But, Wolverine is picking up a lot more users who combine it with EF Core (and JasperFx deeply appreciates these customers!) and I know damn well that there will be more and more demand for this pattern as people with more traditional DDD backgrounds and used to more DI-reliant tools transition to Wolverine. Now was an awfully good time to plug this gap.
If it was me, I would also prefer having an Entity just store published domain events on itself and depend on Wolverine “scraping” these events out of the DbContext change tracking so you don’t have to do any kind of gymnastics and extra layering to attach some kind of IEventPublisher to your Entity types.
Lastly, if you’re comparing this straight up to the MediatR approach, just keep in mind that this is not an oranges to oranges comparison because Wolverine also needs to correctly utilize its transactional outbox for resiliency, which is a feature that MediatR does not provide.






