There are two different aspects in your question: where should the boundary be in your domain model? and how to implement your model?
The boundaries in the model
The definition of an aggregate is:
A cluster of associated objects that are treated as a unit for the
purpose of data changes. External references are restricted to one
member of the aggregate, designated as the root. A set of consistency
rules applies within the aggregate’s boundaries.
Reading your question, it appears that:
Purchase Order has a unique ID, and that identifies the object completely independently of the Contract. By the way, this is normal business practice: any PO must be easily retrieved in any situation, even without knowing the contract (e.g. warehouse workers receiving the shipping of an order do not care about the contract context).
- Furthermore, you say that you could very well add an initiative to the PO directly. This suggest that the consistency of your model doesn't require the Contract to check the consistency.
These facts strongly suggest that
Initiative could be in an aggregate that doesn't include
Contract. You need to confirm this deduction, by cross-check that the contract is not needed when you change the PO, add an initiative or change an initiative. Another useful question is what happens to the POs when the related Contract gets a status "cancelled" or is deleted.
When you speak about hydrating the PO, etc... you are dsicussing about the implementation. This is NOT something to think about WHEN DESIGNING your domain model. There are plenty of patterns to implement an object oriented database application (e.g. identity maps, lazy loading, etc...). But first you need to think about the domain and understand it well.
For instance, if it appears that every change on a PO would require to cross check that some ceilings in the contract are not exceeded, and vice-versa, the change of some limits in the contract requires to check in real time that the new value is still above the total value of all the relevant orders, then a direct change to a PO would seem a little bit risky. It could then be a good idea to consider the aggregate root with the contract after all. You'll always find technical solutions to domain and business challenges. For example, under the mega aggregate assumption a query on PO number could very well return the corresponding aggregate. Up to you to channel the change to the PO through the contract to enforce consistency. You could also use the principle of lazy load to load an aggregate but hydrate only objects that are strictly needed.