Hi there,
as this is my first post on this list, let me quickly introduce myself. My name's Tilman, I am working for a German eCommerce startup. My team and me are creating a CQRS/ES based eCommerce application based on Scala. Our general approach is I think quite in line to what most people on here seem to do - all write integration is purely based on events being dispatched between aggregates and contexts. In addition to that we allow cross-context calls for reading data from other contexts. We implement Ports and Adapters in order to have anti corruption layers in between.
Let me sketch out an example from the eCommerce domain. Consider a context "Purchasing" that is all about doing purchase orders from suppliers, exporting expected stock-receipts to logistic providers and importing actual stock-receipts back. Purchase orders generally contain a good amount of data that originates from other contexts, e. g. contract terms from the "Buying" context, product details from the "Catalog", shipping time frames from the "Shipping" context and so on.
With the above mentioned architectural approach, I see different possibilities for getting the state from other contexts into the "Purchasing" context. Let's think about the use case "create purchase order":
a) We could constantly replicate all state needed for a new purchase order to an aggregate in "Purchasing" by means of dispatching events to that aggregate, e. g. to some "PurchaseOrderTerms". This aggregate would work as a factory for a new "PurchaseOrder" aggregate, passing all data that it has collected from other contexts earlier to the new order during it's creation.
b) We could get all data needed from other contexts via cross-context-calls in the moment the "CreatePurchaseOrder" command arrives at the "Purchasing" context.
c) We could pass all data needed for the purchase order within the actual "CreatePurchaseOrder" command, i. e. the whole content of the purchase order would be submitted by the user already (and of course the form would have been prefilled from data in other contexts, so we kind of move the cross-context-dependency to the application layer).
We were mostly focussing onto a) and/or b) until I recently re-read some of Nygard's articles about maneuverability and semantic coupling, e. g.
http://www.michaelnygard.com/blog/2015/04/manueverability/ and
http://www.michaelnygard.com/blog/2015/04/the-perils-of-semantic-coupling/I found that a) and b) in the end create a lot of dependencies between contexts, although we have ACLs in place. With c) it seemed much easier to think about a completely self-contained "Purchasing" context that does not need to have any knowledge about other contexts' existence, and has a single well-defined entry point.
What we would sacrifice though, is the ability to validate things like "does item X at all belong to contract Y?" (because we do not have any contract information anymore in the "Purchasing" context, or "is the current name of SKU X really Y?" (because we do not query the catalog anymore).
We would simply accept the order in the "CreatePurchaseOrder" command to be in line with contract terms and so on and would only execute "local" validations (e. g. does a drop-shipping purchase order contain a dispatch date for every item and so on).
On the other hand with c) we can be pretty sure that the order in the end is exactly what the user submitted. If we rely on asynchronous state as in a) and b), it could always happen that the user submits an order but some property (e. g. a product name) is affected by concurrent updates in the moment of command submission.
I find this a very interesting trade-off and would be interested in other people's opinions or experiences.