You may want to display the inventory level on the web page, or you may want to display the edition number of the inventory in stock (imagine your inventory is books, magazines etc). This information comes from the Inventory domain.
The main thing to notice at this point is that you are talking about a view, which is to say that using stale data is acceptable.
That being said, you don't need to be interacting with the aggregates (which are responsible for preventing changes from violating the business invariant), but with a representation of a recent copy of the aggregate's state.
So what I would normally expect is a query run against the Product Catalog, and another run against the Inventory, and something to compose the two into the DTO that you need to support the view.
Load both the Product domain and the Inventory domain aggregates?
So that's close. We don't need to load the aggregates, because we aren't going to change anything. But we need their state; so we could load that. That said, I would normally expect the two domains to be running in different processes. Therefore, we'd be calling both, not loading both.
Would you hold some properties on your Product domain entity for number in stock, and edition in stock, and then use Domain Events to update these when the Inventory entity is updated?
"Don't cross the streams. It would be bad."
Using events to coordinate information across domain contexts: great idea. Pushing concepts that belong in one domain into another: opposite of a great idea, except more so.
You want to keep the domains clean. The applications that interact with the domains, it's not so important. So for instance, it is reasonable for the Inventory application to call a service in the product application to query some product specific concepts to add to a view. Or vice versa.
I don't know of any reason that a single application needs to be restricted to a single domain. So long as there is a single source of truth, you can distribute the transactions any way you like.
But just to think this through, in the example above we would end up with potentially 2 DB tables for product catalog and product inventory. Now, do we use the same identifier in these as it's the same product.
That would be the easy way. In larger terms, you use the same identifier because the real world entity is the same; the two different bounded contexts model that entity differently, but the model isn't the real world entity.
When that doesn't work, then you'll need some query to use to bridge the gap. I think the most common variation of this is that the newer entity preserves the id of the older entity. You'll see this within a single BC as well: applicants, when approved, become clients. It's a different aggregate (the state associated with a client is subject to a different invariant than that of the applicant); so if your persistence layer is using event streams, the stream for the new aggregate will need a different identifier. So there will be a bit of state somewhere that says "this applicant became this client".
Or, could we use 1 table and 1 table row for the data and simply map the relevant data onto the aggregate properties?
YIKES! No, don't do that. You're adding transaction contention without any business reason for doing so.
Best Answer
Disclaimer: I am not familiar with Entity Framework Core. I am familiar with Hibernate and Active Record ORMs, though.
First of all, Evans (originator of DDD) suggests to limit associations to be single directed, for clarity and simplicity of implementation. Also he says, that using navigation or db lookup is a "design choice".
So I think, that (single-directed) navigation properties are preferable within aggregate. In other cases you should consider case-by-case how navigation-vs-lookup affects your design: coupling, clarity, simplicity.
Provided that Customer is clearly not a part of Order aggregate, it makes sense to have it lazily loaded. Or to replace 'navigation property' with explicit db lookup.
I think that if you have some ORM annotations on your domain object, it is not a big deal. AFAICT, there is also partial classes feature in C#, so you probably can have ORM part separated from domain part. To maintain a certain level of domain layer purity.
As a meta-comment, I think that the main goal of DDD in context of implementation is to keep business logic clearly expressed (that is you can easily see the intended business logic behind the code) and separated from other concerns. So that to keep domain code understandable and align with the intended model. You start with that you go as far in that direction as you can. When you see that the most pure approach conflicts with something like performance, you do the adjustments, that minimally distort the clarity of the business logic, while achieving the performance goals.