3

I'm having problems defining the aggregate boundaries (and maybe even the aggregate root) to enforce the transactional consistency rule. Let me first describe the problem domain.

In our domain we have a project that contains a budgeted plan, which in turn contains hundreds of purchase items that need to be purchased. Some of these items are selected to form a purchase task. When this occurs, the item should be moved from the budgeted plan to the purchase task. The flow of the item is of importance (needs to be audited for later review), so the item must be an entity and have an identifier.

My current design would have the purchase task as the aggregate root and it would know all its items. I'm inclined to leave the budgeted plan outside of this aggregate as it contains lots of items. But then I began to doubt myself.

Vaughn Vernon describes that a well designed aggregate should be "based on true business rules that require specific data to be up-to-date at the end of a successful database transaction". In my scenario the purchase item that is moved from the budgeted plan to a designated purchase task should be removed from the list of items in the budgeted plan. Now if my aggregate would not include the budgeted plan and all the hundreds of items there, I cannot enforce this rule. If I do include it, the aggregate grows large and will have to load the all the budgeted plans items (hundreds, maybe thousands) for validation to succeed.

As the purchase item is also an entity that needs to be trackable and modifiable I think that should be it's own aggregate root. Then the item should be referenced only by its ID from both the budgeted plan and the purchase task. Is this correct?

The main question is: How should I define my aggregate boundaries and roots to be able to enforce the transactional consistency rule in this domain?

  • Edited the post to clarify the problem and the question. Does it need more clarification, or is it more clear now? – Tuukka Haapaniemi Sep 7 '15 at 7:38
0

When this occurs, the item should be moved from the budgeted plan to the purchase task.

Are you sure? That language sounds really strange.

I would expect a budget plan to act like a shopping list. You plan what you are going to buy, and then when you get to the store you start checking things off the list. Note that this is different from removing the items from the list, which would involve what? tearing the list into pieces so you can take the item at the end and throw it away.

Note that because the list persists, you arrive at checkout and have the ability to compare the contents of your cart to the list.

As the purchase item is also an entity that needs to be trackable and modifiable I think that should be it's own aggregate root.

Which invariants does the purchase item entity need to enforce for itself in this domain?

  • Tanks for the answer! The language is correct and it is so because the budgeted plan includes all the things needed along the project but they are not purchased all at once. That is why it is teared into smaller chunks, that are the purchase tasks. Those purchase tasks are then continued to the end similar to what you describe, but not the whole budgeted plan. Ultimately we will compare the original budgeted plan to the result of the separate purchase tasks to see that we got everything, and more importantly, how much it cost us against the original estimate. – Tuukka Haapaniemi Oct 12 '15 at 10:41
  • A purchase item can be splitted into more than one item (i.e. we thought we'd buy a complete toilet, but instead decide to buy all the materials and work needed to make the toilet) or multiple items can be combined to one item (opposite of the aforementioned example). The invariants are a bit blurry to me as I'm quite new to DDD. The reason I think of the purchase item as an entity is because it has an ID and it has an ID because it has to be tracked through the whole process: Which item in budgeted plan went into which purchase task, how was it splitted and what was the final price etc. – Tuukka Haapaniemi Oct 12 '15 at 10:45
1

Project plan could be an aggregate and budgeted plan and purchase task could be entities inside that aggregate. The project knows all the data it needs to make the decisions. There can be quite a bit of data but you can utilize lazy loading and custom sql queries to optimize memory and cpu usage.

Alternative design: Budgeted plan and purchase task could be aggregates. You save some memory but you now need to modify two aggregates at the same time when you are moving items between the two which probably means you should use eventual consistency between the two aggregates. See here how this can be achived.

  • This got me thinking: To make sure the item is removed from the budgeted plan, all I need is its Id, and hence the performance/memory hit wouldn't be too bad. The downside is that when the process continues after the purchase task to bidding phase, the same questin arises and this model would mean I'd have to enlarge the aggregate with another entity, and another... I'll update my question to include this notion. The alternative design is not too appealing: How can I guarantee the consistency after the operation, if the update to the second aggregate is eventually consistent? – Tuukka Haapaniemi Sep 4 '15 at 5:23
  • Actually, with the process continuing, the whole purchase task is the one that goes from stage to another and it propably is not necessary to check that it's items are no longer in the budgeted plan, or what do you think? Then my purchase task could be the second aggregate root and bidding the third etc. How does this sound? – Tuukka Haapaniemi Sep 4 '15 at 5:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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