0

I have three aggregates Product, Seller, Buyer. The buyer offers to buy a product from the seller and the seller can accept or reject the buy offer. So the process for making the offer is this: in the buyer aggregate I check whether the buyer has already made offer too the product, then I check in the product aggregate if its status is in sale and check in the seller aggregate if the buyer is banned (the seller aggregate has list with banned users). If all checks are true the saga create new offer. But what if after I have checked whether the buyer is banned the buyer gets banned? The seller will ban a user and after that he can still receive an offer from the user?

manfrom
  • 91
  • 7
  • Your question is based on the premise that your design (aggregates and saga) is correct, but I'd argue that it's not. Seller and Buyer are not aggregates, but actors. And the saga is most likely the aggregate. The aggregate implements the "offer" process and keeps the consistency. – Francesc Castells Aug 27 '22 at 17:01
  • What if the aggregate becomes too big and I need to split it? Can I use my design somehow? – manfrom Aug 27 '22 at 19:27
  • Well, of course, you can do whatever you want/need to get the job done, but in general, If the aggregate becomes too big, you'd need to rethink the aggregate design or use a different pattern, but I'd still argue that having 3 entities coordinated with a saga is not really using aggregates. In any case, I understand that your main question is about the apparent race condition. Some of these scenarios disappear with proper aggregate design (because the aggregate enforces the consistency), but in this particular case, you probably need to solve it with events and eventual consistency. – Francesc Castells Aug 30 '22 at 20:43

1 Answers1

1

Races are an inevitable consequence of designing a system with distributed decision making authority.

In other words, it takes time for the information that a particular shopper is banned to travel from the shopkeeper who made the decision to the centralized model. So in just the same way that we have to handle the case where we send an offer to the shop nanoseconds before the shop bans the shopper, so to do we need to handle the case where we send an offer in the nanoseconds between when the shop bans the shopper and when that information gets integrated into the domain model.

This is part of the tradeoff we accepted when we chose a distributed system in the first place.


As far as I can tell, we manage this mainly by setting expectations. "Bans shall be announced five minutes before they take effect", or whatever, to give the information time to move around your system.

Expectation setting might use the language of service levels (99% of all bans are effective within five minutes).


Mostly, you're going to be managing tradeoffs - how important is respecting a recent ban compared to expediting the delivery of offers? If you don't need low latency delivery, then you can afford to wait a little while to see if a ban shows up.

(If you'll look carefully, you'll see that there is still a race condition present - what we're really manipulating is the business significance of the race. See Udi Dahan's Race Conditions Don't Exist)


In a local setting, if you really need tight control of the sequence of bans and offers, then you need to have a single lock in the system shared by the code that processes bans for a particular shop and the code that processes offers for a particular shop.

That doesn't prevent the race, of course (you get different behaviors depending on whether ban acquires the lock nanoseconds before the offer or nanoseconds after), but does at least give you a clear "happens-before" relationship in your audit log.

VoiceOfUnreason
  • 52,766
  • 5
  • 49
  • 91