2

I am aware there is a lot of topics on set validation and i won’t say i have read every single one of them but i’ve read a lot and still don’t feel i’ve seen some definite answer that doesn’t smell hackish.

Consider this:

  • we have a concept of Customer
  • Customer has some general details data
  • Customer can make Transaction (buying things from the store)
  • if Customer is in credit mode then he has a limit of how much he can spend in a year
  • number of Transactions per Customer per year can be huge (thousands+)
  • it is critical that Customer never spents a cent over a limit (there is no human delivering goods that would check limit manually)
  • Customer can either create new Transaction or add items to existing ones and for both the limit must be checked
  • Customer can actualy be a Company behind which there are many Users making actual transactions meaning Transactions can be created/updated concurrently

Obviously, i want to avoid loading all Transactions for Customer when creating new or editing existing Transaction as it doesn’t scale well for huge number of Transactions.

If i introduce aggregate dedicated to check currentLimitSpent before create/update Transaction then i have non-transactional create/update (one step to check currentLimitSpent and then another for create/update Transaction).

I know how to implement this if i don’t care about all ddd rules (or if its eventual consistency approach) but i am wondering if there is some idiomatic ddd way of solving this kinds of problems with strict consistency that doesnt involve loading all Transactions for every Transaction create/update?

dee zg
  • 13,793
  • 10
  • 42
  • 82

1 Answers1

2

it is critical that Customer never spents a cent over a limit (there is no human delivering goods that would check limit manually)

Please read this couple of posts: RC Dont Exist and Eventual Consistency

If the systems owners still think that the codition must be honored then, to avoid concurrency issues, you could use a precomputed currentLimitSpent stored in persistence (because no Event Sourcing Tag in your question) to check the invariant and use it as optimistic concurrency flag.

  • Hidrate your aggregate with currentLimitSpent and any other data you need from persistence.

  • Check rules (customerMaxCredit <= currentLimitSpent + newTransactionValue).

  • Persist (currentLimitSpent + newTransactionValue) as the new currentLimitSpent.

  • If currentLimitSpent has changed in persistence while the aggregate was working (many Users in the same Company making transactions) you should get a optimisticConcurrency error from persistence.

  • You could stop on exception or rehidrate the aggregate and try again.

This is a overview. It can not be more detailed without entering into tech stack details and architectural design.

jlvaquero
  • 8,571
  • 1
  • 29
  • 45
  • thanks for your nice explanation! just one clarification, doesn’t this again introduce 2 step, non-transactional edits: one to update `currentLimitSpent` and one to place actual `Transaction`? – dee zg Aug 30 '19 at 09:41
  • The aggregate checks the currentLimitSpent rule and any other rule to create the Transaction. Its response will be Error or OK. On OK the aggregate response contains all the changing data that need to be persisted to keep the system in a consistent state. This is just one transaction per aggregate. In the persistence side you should have a mechanism that allows you to open a persistence engine transaction-> persist new transaction->persist currentLimitSpent->commit/rollback transaction. – jlvaquero Aug 30 '19 at 09:50
  • Try to do not mix the transaction concept of an aggregate (business transaction) with the transaction of a persistence engine. – jlvaquero Aug 30 '19 at 09:54
  • yes i see, so it relies on transactional capabilities of persistance technology? thats one of the solutions i had on mind, pretty common, but then i thought thats a bit out of ddd order because it tightly couples 2 aggregates and potentially leaks business rules out of domain. (not sure about this, just stating my impression) – dee zg Aug 30 '19 at 09:55
  • "because it tightly couples 2 aggregates" may be a indicator that you need to redesign your aggregates... – jlvaquero Aug 30 '19 at 09:57
  • i guess that my question - how?:) – dee zg Aug 30 '19 at 09:59
  • Maybe this could help: https://stackoverflow.com/questions/51243959/how-to-properly-define-an-aggregate-in-ddd/51299995#51299995 – jlvaquero Aug 30 '19 at 10:01
  • 1
    "leaks business rules out of domain" - leaks where? Into persistence layer? The persistence layer just uptade a few columns in a few tables with an "all or nothing" behaviour. There are no business rules there. – jlvaquero Aug 30 '19 at 10:06
  • 1
    This could help too: https://stackoverflow.com/questions/56682746/aggregate-root-invariant-enforcement-with-application-quotas/56700736#56700736 – jlvaquero Aug 30 '19 at 10:08
  • this last link is somehing i haven’t seen before and its very helpful, thank you! – dee zg Aug 30 '19 at 10:42