9

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?

7

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.

  • I have ticked this as the answer, credit also to @guillaume below who also pointed out that reading data to display in a view doesn't require loading up the aggregates. Thanks for such a long detailed answer. Coming from the "traditional" data model first approach I have found it difficult forcing myself to forget the persistence layer and focus on the domain language. Just when I think I have got it, I read another article which blows my understanding apart. – PendorPaul Feb 1 '16 at 20:43
  • I just read this article msdn.microsoft.com/en-us/magazine/dn802601.aspx which details using Domain Events to duplicate some data between contexts. The example being sharing a customer list between customer service and an order processing system. This is duplicating customer data in the order system, and using Events to sync the data. This flies in the face of what we have answered here. Surely in the linked sample the order context just needs a customerID, and that can be populated from the application which has access to the customer service context. – PendorPaul Feb 1 '16 at 20:47
  • You'll want to be very precise in your thinking, here. Coping data between systems is NOT the same thing as copying data between domain models. Any time you see "read only [mumble]", it's a big hint that the data doesn't belong in that domain (even if the application might still care about it). – VoiceOfUnreason Feb 1 '16 at 21:54
  • Yes, that is what I thought too. The thought process shift is difficult enough without "experts" publishing articles which muddy the waters. I've spent today really analysing my domain, to try and really fully understand where my BC and Aggregate Roots lie. I am pretty much there, but also I know I can refine and refactor my model as I go. I have been stuck in over analysis hell for days now, worries to start writing code fearing that I don't have DDD straight in my head. I think it's best to crack on, and keep questioning my code, and whether the model is right. I will get there. Thanks – PendorPaul Feb 1 '16 at 22:59
  • Maybe I misread, but the practice I believe you're speaking to is called Event-Carried State Transfer ( Event-Carried State Transfer) and it can be a very powerful model especially for dashboard/table views where you need to filter and page data across services. You can build "projections" (basically views) from domain events to drive these dashboards. I've used it before quite successfully. It can be a very powerful tool in the right scenario. IMHO what's more important here is that you realize that these projections don't represent domain models and they should not contain business logic. – Jordan Oct 4 at 11:36
3

I think your question really calls for 2 orthogonal sets of options -

  • Do you load two objects and present their data together or do you load 1 object that contains everything you want ?

  • Do you use aggregates for displaying stuff, or something else ?

If you believe in the CQRS approach, it turns out that aggregates may not be the best bet for reads. Each time you load an aggregate, whether to display its data or modify it, you add concurrency and contention to your system. Also, aggregates are potentially bulkier and slower to load than if you use ad-hoc read models tailored for display.

Solution a) from your Q seems subject to a lot of these pitfalls. Option b) can be valid, but I would use it only if data from the InventoryManagement BC is needed to enforce invariants when mutating the Product aggregate. It's better if an aggregate contains all the data needed to check its business rules upon modification, but on the read side they can sit anywhere.

Regarding data, a common recommendation is to give Bounded Contexts their own database (for deployability and SoC reasons). You'll probably have to use the same identifiers if you want to match products between the two BC's.

About cross-BC interactions, you might also want to have a look at https://stackoverflow.com/questions/16713041/communicating-between-two-bounded-contexts-in-ddd

  • 1
    OK, That helps. Essentially we are using Aggregate Roots and BC to ensure our invariants are consistent, and our data is valid, and the operations we want to perform are wrapped up within our aggregate or BC. When it comes to reading and displaying that data, we can handle that separately and lightweight without all the aggregates/BC being hydrated. After all, why do we need to load the Aggregate when all we are doing is displaying the data either in a report or onscreen. This makes perfect sense. Thanks. – PendorPaul Feb 1 '16 at 15:09
  • This is where CQRS really really shines. You can just do a SQL query, join the tables, and return the tailored query for a specific view. It also clears your repo from query method mess. Also, you can even replicate data on service like ElasticSearch and query against it. – doesnotmatter Jun 3 at 17:25
1

DDD is meant for applications where business logic is complex. "print something" is not a complex business logic. It is actually not a business logic at all.

If, the business logic in one context needs some information to properly handle some use case, then that information is part of that context. So the idea that one bounded context might need information that is available in different bounded context doesn't make sense, because bounded context has all the information it needs.

  • OK, so take something like Amazon, that's a complex system with complex business logic. They have catalog management and inventory management. They don't need inventory totals to manage the catalog, by that I mean the product name, description, type, condition etc. However, they do show the number of items in stock on their store front page. In that scenario, where you want to separate your Product Catalog Management and Product Inventory domains, but you need to display some information about inventory on your product page, how do you do it? – PendorPaul Feb 1 '16 at 12:45
  • I guess what I am saying is the Product Catalog domain has all the information it needs to manage the product catalog. The Inventory domain has all the information it needs to manage inventory. However, when I want to display some information to a user, and that data comes from 2 domains, where do I do that. Do I just load up both domains in my UI and bind the properties I want to show? Or do I have some reporting/reading mechanism where I return an anonymous type or DTO containing the data I need for my UI? – PendorPaul Feb 1 '16 at 12:50
  • It's comical looking back at these old comments of mine 11 months ago, but seems like a lifetime.You were completely right Euphoric about the read and write aspect. The application I am working on has evolved quite a bit as my understanding has evolved. I'm now using CQRS methods, through Jimmy Bogards Mediatr. The sheer flexibility of having commands which interact with Aggregates, but then using queries and query handlers to bring back whatever I need for display is incredible. Wrap that up into Views which call into those QueryHandlers, and the decoupling is good with this one. Thanks – PendorPaul Dec 15 '16 at 14:47
  • @PendorPaul, I think I'm where you were 11 (now 13) months ago. What got you to where you are now? – Greg Bell Mar 15 '17 at 0:36
  • 1
    @GregBell You need to force a mindset change. I was stuck in the approach of "design a database, build a data tier, build some business logic... etc". And I was focused on creating all encompassing entities. i.e. a "Product" in an eCommerce site, which handled everything from pricing, description, inventory, stock location.... but that becomes extremely complex. The bounded context approach means in the Inventory context, the "Product" only contains info and behavior for inventory management. Description and images are managed in a content context, pricing in the pricing context. – PendorPaul Mar 19 '17 at 11:41
1

From my point of view there are different defintions of "Product" - every bounding-context hast its own definition of "product"-domain :

  • In the Content-Management-Bounding-Context a product has an image and a description text.
  • In the Inventory-Bounding-Context a product has stock-quantities, product seller, forcasts when the product will be availabile
  • In the Price-Caculation-Bounding-Context there are rules how much a product may cost per quantity.

On top of these i would add an additional Shop-Bounding-Context with its own product-defintion (a relevant combination of the product-domains of the other Bounding-Contexts).

A Shop-Product would have "image and a description text" from content and availability from "Inventory" but not "product seller" from inventory.

This additional Shop-Bounding-Context depends on the Bounding-Context-s Content, Inventory, Price

  • So how are you creating that Shop-Product BC? Within that context do you have a reference to the Product and Inventory BC, and are you hydrating those from your persistence store when you load up a Store-Product BC, and then offering up the properties you want from those BCs through your StoreProduct properties? I found this article actually which is along the lines of what I was thinking, cross BC events but @guillaume13 above has pointed out that for display purposes I can avoid the BC and just pull back the data I need for my view.msdn.microsoft.com/en-us/magazine/dn802601.aspx – PendorPaul Feb 1 '16 at 15:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.