Detach event listeners for domain events? or how to stop executing otherwise required post events on specific use-cases

StackOverflow https://stackoverflow.com/questions/16649128

Question

So let's take the usual Order example. Assuming a rich domain model we have an Order.place() call. It seems the way to do additional tasks related to this action these days point to domain events. So let's say this call fires an "OrderPlaced" event. The usual thing that happens after the event is placed is that we send a confirmation email so we create an event listener for this event, and send an email.

So simply: Order.place() > OrderPlaced event raised > EmailForOrderPlaced listener fires > Email gets sent

We also have registration that works in a similar way (User.register() > UserRegistered event raised > Registration listener fires > Email gets sent)

However:

The problem (a made up task - doesnt have to make sense - however lot of real business requirements don't anyway):

Now we want to offer registration + order functionality in one, and opposed to the regular one email per registration + one email for the order, we only want ot send one email that contains both. This is an aggergated concern so normally we would create a domain service that does this, however if we call User.register(); Order.place(); now it will fire the 2 events as it would normally, spamming the customer with emails (ok, not really, but this is an example)

So how do we get around this? Obviously sending the combined email is not a problem because we can raise an event for that in the service, but that still leaves us with the original 2 emails. If I detach the 2 listeners in the service before I execute the 2 calls, that would actually mean that I have to know about their functionality and every time a new one gets added I have to go back to the service detach that one as well, etc, is there a better way that would seamlessly do this?

Was it helpful?

Solution

You have a few options.

First of all, make the use-case explicit. You can, for example, add a bool to the UserRegistered event indicating that the user was registered as part of creation of an order. This would allow the email handlers to send appropriate emails.

Another option is to create a handler which sends emails for both events. Since emails shouldn't be sent immediately anyway have the handler, upon commit, determine which events have arrived during a given unit of work and in this way, determine the type of email to send. If both events have arrived, send a single email, otherwise send the email corresponding to the event received. For this to work, you have to make sure the handlers have a per-unit-of-work lifetime.

Finally, digging deeper, observe that you have to aggregates at play, a User and an Order. Ideally, especially in distributed scenarios, you shouldn't modify to aggregates within a single transaction. To implement a use-case requiring modification of to aggregates, create a saga also known as a process manager. With a saga, each aggregate gets modified in its own transaction and as part of the transaction and a message is sent to advance to the next step (in this case there are 2 steps). The saga would receive an initiating message called something like RegisterUserAndCreateOrder. It would then register the user, then the order, and upon completion of both, it would send an appropriate email. Note the user and order can even be created concurrently. Take a look here for more on this event driven approach.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top