DDD is a technique for solving complex business problems.
When creating a domain model, forget about the relational database.
Start with drawing a model on a whiteboard together with a domain expert. This version will definitely not have a concept called
ProductMaterial. It will contain a line between Product and Material with a note that every Product has various materials with different quantities. Thoroughly ask questions about this relationship; is there a name for this concept? Perhaps Bill of Materials? What kind of products have 0 materials? Should we treat those differently? Is that perhaps a related but different concept in the model? Etc. Etc.
Next convert this model to code, again without considering the relational database.
The Product Aggregate Root could contain the Bill of Materials concept, which could be a ValueObject that contains the information about the amount of materials. ValueObjects are immutable, so when the bill of materials for a product changes, it is replaced with a new instance.
It’s also possible that it turns out the bill of materials should be promoted to an Aggregate Root. It would get it’s own Id and has a ProductId in order to connect the two aggregates.
Domains can be modeled in different ways and it’s your job, together with domain experts, to find the model that best fits the needs for the business.
Eventually you’ll want to store the aggregate in a database.
If you use a NoSQL document db, simply store an aggregate as document. More advanced systems combine this with specialized read databases, that get updated with domain events.
If you prefer a relational database, you’ll have to create a mapping between the domain model en the relational model. ORMs can help with that, but be careful not to ‘leak’ the database details into the domain layer. The idea is to keep the domain model focused on the business needs, not the technical details.
One of the big advantages of using an ORM is that you don’t have to write your own change tracking. If you have an aggregate that contains a collection of entities, it can be a daunting task to detect changes to those entities.
You also asked about loading a lot of data when you want to change an unrelated property in an aggregate. Should you still load all data? The answer is yes, the caller of a domain method should not have (implicit) knowledge about what parts of an aggregate should be loaded. An aggregate should always be fully loaded. For this reason aggregates should be kept small.
An ORM can also assist in this scenario if it provides lazy loading. From a purely technical point of view, the aggregate is not always fully loaded when using lazy loading, but from a practical point of view, as soon as the data is actually used, it will be available. The client of the aggregate can simply assume the aggregate is fully loaded.