0

I am designing an aggregate that has list of keywords as its property, and it has constraint that two aggregate cannot have same keywords as its property.

I use repository that has save and saveAll method when persisting aggregate, and each run in a single transaction. And Factory pattern is used when creating an aggregate.

When creating a single aggregate, the validation would be easy that i check if keyword is owned by others through KeywordChecker service that internally query the existing record for the validation.

The problem comes when i create multiple aggregates, in this case not only checking existing record with KeywordChecker is required, but also checking pre-created, but not yet persisted aggregates is required, because KeywordChecker will only check the records that are persisted.

I could put another validation logic that resembles KeywordChecker but that will lead me to duplicate service just working on a different dataset. Is there any elegant way to handle this issue?A

12
  • What should the behavior be when a) a user modifies one aggregate and creates a second in one action? For example, move a keyword to a new aggregate. b) Different users create new aggregates at the same time that contain the same keyword? Dec 19, 2023 at 8:59
  • The rule is that aggregates are not allowed to own same keyword. a) If user did not persist he first aggregate, then KeywordChecker will still check that the keyword is owned by the first aggregate, and user will not allow to create second one. b) That should not be allowed. If i use unique constraint of database, it will be easy to do so, but then our domain model will miss the important business concern(that the same keyword cannot be owned by multiple aggregate) .
    – ringord
    Dec 19, 2023 at 9:31
  • Have you considered alternative domain models, for example an aggregate that can enforce this business rule without depending on infrastructure?
    – Rik D
    Dec 19, 2023 at 17:59
  • @RikD, I tried, but since the aggregate is persisted in multi-thread environment, i found no way to enforce this without depending on infrastructure.
    – ringord
    Dec 20, 2023 at 2:21
  • @ringord Can you clarify what you mean by "The problem comes when I (single user ?) create multiple aggregates"? If you create multiple aggregates as part of the same business transaction, you can check them exactly like you do with one, right? One could argue that the real problem is when multiple users claim the same keyword in concurrent transactions, which is harder to control... Dec 20, 2023 at 10:48

1 Answer 1

3

As you have stated, once the data is persisted it's not really a problem - any subsequent action can simply check if a conflicting record/aggregate exists.

Fundamentally the problem you are facing is one of distributed locks - you need to take a lock early on in the processing, before you are ready to persist the primary data/transaction, so that other operations can have visibility into the existence of the lock.

In order to achieve that you need to ensure that the lock is not part of the primary transaction - that might be a second DB connection (if you want to use the database for managing the lock) or you could use a dedicated service to manage the locks (such as Redis) I am assuming that the primary transaction will commit in some reasonably short amount of time so you can use a simply strategy such as locks expiring after 10 seconds to ensure that you don't end up with orphaned locks, if there is some bug in your code.

Edit: Integration of Locks into the design

From a business perspective I would consider the Keywords to be a finite resource - i.e. there is only one of each that can be REFERENCED in the main aggregate. I see this as analogous to Orders and Inventory i.e. if the business requirement is that items can only be added to Orders if they are in stock, it becomes necessary to update both aggregates in sync (i.e. using domain events to propagate updates across aggregates).

However the point of Aggregates is that they are transaction boundaries, so in the case of the inventory example, you would probably reserve the inventory first, assign it to the order then decrement the inventory (and reservation) after the order has been shipped (i.e. the reservation remains in place until you no longer have the physical item in the warehouse).

Now applying this to keywords I think the root of the problem is the keyword is actually it's own aggregate, albeit a tiny one with almost no functionality. If you were to implement this purely as a DDD solution I think you would use a table just to store the keywords that were in use (or about to be used).

Your KeywordChecker service is conceptually the repository for Keyword aggregate - I understand it doesn't really make sense to create a table for this limited functionality (and implement all the logic to update it correctly), however I think you can still expose it as such. I.e. that the implementation of the persistence of keywords is based on a lock and checking the existence of those keywords in the main aggregate rather than using its own storage.

7
  • Thank you for the answer, How should i adapt lock into my domain logic? The first thing comes into my mind is creating a KeywordLock domain service that has lock, and unlock method receiving keyword as the parameter. And the KeywordChecker service will check both db and KeywordLock service(by checking if it can acquire a lock or not) within its check method. And my factory and aggregate will use the KeywordLock service. Nonetheless, it seems like that i brought infra concerns into my domain logic, since my domain has nothing to do with the term lock.
    – ringord
    Dec 20, 2023 at 6:14
  • Edited answer to answer this locking question.
    – DavidT
    Dec 20, 2023 at 8:19
  • Thanks, instead of KeywordChecker service, i could use KeywordPool that has pull and push method, that pull will check the availability of keyword, acquire the lock, and push will just release the lock. And my factory will depend on the KeywordPool. And while writing this, a new concern comes into my mind. Although the aggregate created by factory is created, it isn't actually created in the whole system until it is persisted. So if the created aggregate is not persisted due to connection/storage error, the acquired lock must be released. continued...
    – ringord
    Dec 20, 2023 at 9:16
  • To do so, not only the factory depends on the KeywordPool but also the domain orchestration method(I call this task handler) needs to depend on KeywordPool to release the lock (if i use lock without ttl feature). It feels like having another transaction boundary surrounding our task handler. Would that be ok?
    – ringord
    Dec 20, 2023 at 9:23
  • @DavidT before talking about locks, reservations and advanced concurrency management of the kind used by online stores, shouldn't we look at the domain context? What is the contention for these keywords and the impact of conflicts? If it is low and conflicts are rare, optimistic concurrency control - basically boiling everything down to "once the data is persisted" - seems like a sensible option. Dec 20, 2023 at 14:21

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.