How to handle domain events that are raised by event handlers?
Asked Answered
W

2

11

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();
}
Wheen answered 31/10, 2014 at 15:4 Comment(0)
M
17

Yeah, you misunderstood things. A Domain Event is not like a C# event, it's a message about what changed in the Domain. One rule is that an event is something that happened, it's in the past. Thus, an event handler simply can't (it shouldn't) change the event, it's like changing the past.

You CouponActivatedHandler should at least get the User entity form a repository then update it with the number of credits, then save it, then publish an UserCreditsAdded event. Even better, the handler should just create and send a command AddCreditsToUser .

With Domain Events pattern, an operation is simply a chain of command->event-> command-> event etc. The event handler is usually a service (or a method in one) which takes care only of that bit. The sender of the event won't know anything about the handler and vice-versa.

As a thumb rule, a Domain object generates an event when its state has changed. A service will take those events then send them to a service bus (for a simple app, a DI Container is enough) which will publish them to be handled by anyone interested (this means services from the local app or other apps subscribed to that bus).

Never forget that Domain Events is a high level pattern used when doing the architecture of an app, it's not just another way to do object events (like C#'s events).

Menchaca answered 31/10, 2014 at 19:2 Comment(8)
Thanks, you've clarified multiple things for me. Are you aware of an example project designed the way you describe? I think I get the idea, I'm just unsure of how to implement it properly.Wheen
Example, not really. But start doing it, practice makes perfect. Do, notice what feels cumbersome, try to refactor etc. DDD is learned by doing it.Menchaca
I get it now, specifically that event handlers react to things that occurred in the past explained a lot.Wheen
You wrote then publish an UserCreditsAdded event, it returns us to original question: how should we handle events that are generated by eventhandlers.Clein
@PopovSergei You handle it according to your needs. Using ES,CQRS means you have a projection to update. Or that event triggers the next step of a business process.Menchaca
I have an application with domain model, but i don't implement ES or CQRS - it was a decision to use only domain model and domain events. Every operation is handled as UoW. One operation can span a couple of aggregates. It's usual for me to generate an event and in eventhandler change another aggregate, who generate another one event. So i need cyclic event handling like @Wheen wrote. I've been looking around for awhile and haven't seen any example in web with cyclic handling of domain events. So i'm concerned what i'm doing wrong? Can domain model with events work without ES or CQRS?Clein
@PopovSergei it's better to post that as a new question since is unrelated to the OP's question. You'll get more answers tooMenchaca
@Menchaca Sorry for digging up this question, but I had the same question and your answer helped me a lot to make things clear on how domain events should work. I have one more question regarding your answer: What happens if some of the command handlers/domain event handler that get invoked from the initital handler fail to execute? How do we revert the SaveChanges that took place so that we don't let our domain be "half" updated? Wouldn't it be better if we handle all the consecutive commands/domain events first, and SaveChanges after? So that if any handler fails, no changes will be saved.Zalucki
T
0

The linked implementation of domain events does not handle potentially nested events raised by event handlers. If you read through the authors comments, he himself once mentioned that he "tries to avoid it".

enter image description here

IMO, what you implemented with the loop makes sense. But if you are not certain about getting into an indefinite loop, I suggest you implement a circuit breaker pattern there.

Tutt answered 25/7, 2022 at 6:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.