Domain Driven Design – Cross Domain Interaction Explained

domain-driven-designdomain-model

I am a relative DDD newbie, but I am reading anything and everything I can get my hands on to boil out and distill my knowledge.

I came across this DDD question, and one of the answers has me intrigued.

DDD Bounded Contexts & Domains?

In one of the answers the poster gives the example of an ecommerce system with products being in at least 2 domains:

1) Product Catalog
2) Inventory Management

OK, that all makes sense, i.e. in your ecommerce front end you are interested in displaying the product information, and not interested in inventory management.

BUT. 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.

So, how would you handle this? Would you

a) Load both the Product domain and the Inventory domain aggregates?
b) 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?

One final question. I know we are meant to forget/ignore the persistence of the domain and just think about the domain. 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. Or, could we use 1 table and 1 table row for the data and simply map the relevant data onto the aggregate properties?

Best Answer

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.