0

I'm using event sourcing in an application and constantly find a problem for which I do not find a good solution.

In this scenario we have an application for a hair stylist. We have a CustomerAggregate which performs all actions for a customer. But some times we need to find a range of specific customers, and for that we use projections. Let's say that we need to find all customers with red hair. For that scenario we have a projection (which is a flat database table), which may look something like this:

CustomerId   | HairColor    | Name
1              Red            Alice
2              Blue           Bob
3              Red            Cecilia

The aggregate has a method called ChangeHairColor(newColor), which will apply an event called HairColorChanged. When the event HairColorChanged occurs the projection is updated. This works fine. We can query the projection and find all customers with a red hair color if we for example need to make a newsletter targeting that audience.

My problem occurs when my domain logic is depending on a projection. Writing to the projection is async and occurs outside of my domain logic. Let's consider this scenario:

1. Get all customers with red hair.
2. Dye hair to blue for all those customers.
3. Get all customers with blue hair.
4. Send newsletter to all customers with blue hair telling them what a nice color they have!

When we have iterated through step 2 the update of the projection is async and may not be completed before step 3 occurs. Therefore we may not get the correct customers in step 3. We have a race condition.

How do I properly address this issue? As I understand it a projection should be separated from the domain logic. But in this case the domain logic is dependant on the projection. Should I therefore include updating the projection in my CustomerAggregate?

12
  • Are all 4 steps performed as part of the same command? Or are they actually two separate commands, one that changes the hair color and one that sends the newsletter?
    – Rik D
    Nov 16, 2020 at 12:29
  • @RikD - This is an simplified example and in this particular case it would indeed be more logical to separate it into separate commands. But in this case, yes, it's the same command.
    – smoksnes
    Nov 16, 2020 at 12:30
  • Why do you have command to send an email to a subset of people, rather than attaching the mailsender to the HairColorChanged event itself, so that each person will be emailed as their hair color is changed? I guess what I'm asking is if there's anything preventing you from doing it that way, which ties into the general "separate command" feedback you've gotten but also limits the possible solutions to the problem.
    – Flater
    Nov 16, 2020 at 14:53
  • Wouldn't SendEmailToCustomersWithBlueHair be an event? therefore the email goes to the customers who have blue hair when that event occurs?
    – user253751
    Nov 16, 2020 at 19:19
  • Am I missing something here? What if... you just didn't query a projection? Your command model should never use your read model as a data source. What does CQRS stand for again? Nov 16, 2020 at 21:39

2 Answers 2

1

An observation: "simplified" problems are rarely a good idea in DDD, because the details of the problems tend to matter, and contrived examples rarely have the details fleshed out.

How do I properly address this issue?

For the problem as described, you would probably do something like this: at step #1, you would capture a snapshot of some pool of your customers that are going to be participants in this process, and then from that point forward you would handle each of those customers in parallel, performing the update, sending off the email, and so on. The "projection" that describes their hair color would be updated asynchronously, until eventually it was again consistent with the information in your book of record.

If the process itself were important, then you would have a stream of events for tracking it, and projections off of that stream so that you could see whether the process was complete, what works is outstanding, and so on.

But this report, like all of the others, is going to be a snapshot of information that is not necessarily synchronized with what information is "live". For instance, you might be looking at a report that shows you our best guess at the progress as of close of business the previous day.

In any distributed system, reports are going to be stale - the human reader is probably sitting a nanosecond from the display, the report needs time to copy across the network, and so on. Part of what we need to work out, in our designs, is how much "stale" the business can tolerate, and whether or not we can mitigate the mistakes that are made when we process stale data.

This isn't specific to event sourcing, of course. We have exactly the same sorts of problems when our book of record is a relational database. The big difference between the two cases is that relational databases have general purpose locking strategies available that allows us to coordinate writes in a way that may not be available in our event store (aka locking multiple streams to make a coordinated change).


A useful reference is Pat Helland's paper Data on the Outside vs Data on the Inside. Another to review would be Udi Dahan's Race Conditions Don't Exist.

0

I think you should take a careful second look on your scenario's. It might be an artifact of the analogy you choose, but I believe what you describe is 2 scenarios executed one after the other, rather than a single scenario.

If you consider "Dye all customers with red hair blue" and "congratulate all customers with blue hair" as two scenarios that sometimes are performed in sequence, then it becomes clear that some coordination might be needed between those scenarios, but also that that coordination might be done on a higher level. In some rare cases, it might even be pushed to the user.

On the other hand, if the requirement had been that only the customers who formerly has red hair get the newsletter, then you could just have re-used the set of customers you retrieved in step 1.

2
  • Yes, I believe you are right. This is an oversimplified example. But even if this is separated into two commands. How do you guarantee concurrancy between these two? There is no guarantee that the projection has been updated before the second command runs. Am I using projections wrong?
    – smoksnes
    Nov 16, 2020 at 12:34
  • @smoksnes "Am I using projections wrong?". Yes. Nov 16, 2020 at 22:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.