I've implemented the following pattern by jbogard:
http://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/
Imagine the following entity Coupon
and event CouponActivatedEvent
:
public class Coupon : DomainEntity
{
public virtual User User { get; private set; }
// ...omitted...
public void Activate(User user)
{
if (User != null)
throw new InvalidOperationException("Coupon already activated");
User = user;
Events.Add(new CouponActivatedEvent(this));
}
}
The following event handler CouponActivatedHandler
:
public class CouponActivatedHandler : IDomainEventHandler<CouponActivatedEvent>
{
public void Handle(CouponActivatedEvent e)
{
// user gets 5 credits because coupon was activated
for (int i = 0; i < 5; i++)
{
e.Coupon.User.AddCredit(); // raises UserReceivedCreditEvent and CreditCreatedEvent
}
}
}
The following SaveChanges
override on DbContext
(Entity Framework 6), taken from jbogard's blog post:
public override int SaveChanges()
{
var domainEventEntities = ChangeTracker.Entries<IDomainEntity>()
.Select(po => po.Entity)
.Where(po => po.Events.Any())
.ToArray();
foreach (var entity in domainEventEntities)
{
var events = entity.Events.ToArray();
entity.Events.Clear();
foreach (var domainEvent in events)
{
_dispatcher.Dispatch(domainEvent);
}
}
return base.SaveChanges();
}
If we now activate a coupon, this will raise the CouponActivatedEvent
. When calling SaveChanges
, the handler will be executed, and UserReceivedCreditEvent
and CreditCreatedEvent
will be raised. They won't be handled though. Am I misunderstanding the pattern? Or is the SaveChanges
override not appropriate?
I've considered creating a loop that will repeat until no new events are raised before moving on to base.SaveChanges();
... but I'm worried that I will create endless loops accidentally. Like so:
public override int SaveChanges()
{
do
{
var domainEventEntities = ChangeTracker.Entries<IDomainEntity>()
.Select(po => po.Entity)
.Where(po => po.Events.Any())
.ToArray();
foreach (var entity in domainEventEntities)
{
var events = entity.Events.ToArray();
entity.Events.Clear();
foreach (var domainEvent in events)
{
_dispatcher.Dispatch(domainEvent);
}
}
}
while (ChangeTracker.Entries<IDomainEntity>().Any(po => po.Entity.Events.Any()));
return base.SaveChanges();
}