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?