E-commerce – Who Owns Orders in a Consumer-Provider Marketplace?

domain-driven-designdomain-modele-commercemicroservices

We are developing an application where providers can offer their products and consumers can buy them (sort of marketplace). We try to apply DDD concepts into our model design and the implementation follows a microservices style. This implies that the data belongs to a Bounded Context (BC) and only the microservices within that BC can access it. Outside that BC, specific information can only be either queried through a public interface of the BC or by subscribing to events published by that BC.

My question is about the design of the Orders. Orders are placed by consumers and accepted and fulfilled by providers. They can also be manipulated by customer service. An order right now contains only products from a single provider, but I might be asked in the future to support buying from multiple providers at once.

All implementations I've seen of similar systems contain a single Order model, which tends to be really bloated with information about the products, the provider, the consumer, invoicing, deliveries, payments, etc. I am trying to avoid that, but I am facing the question of "Who owns the order"?

I can think of the following answers:

  1. There is an Orders bounded context which is accessed by both the consumer and the provider. This means that the consumer API has a Place Order operation that talks to the Orders BC and creates an order and the Providers API has an operation like Accept Order which talks to the same Orders BC and changes the status of that same order model.
  2. There are 2 Orders BCs: Consumer Orders and Provider Orders. The Consumer API places an order in the Consumer BC. This creates the order and publishes a ConsumerOrderCreatedEvent. The ProviderOrders BC listens to that event an creates a local Order (ProviderOrder) which references the ConsumerOrder. Through the Provider API, the order can be accepted, which will publish a ProviderOrderAcceptedEvent, which will allow the ConsumerOrders to mark the order as accepted and notify the consumer about it.

My personal preferred approach is option 2 as I can see several benefits (see below), but I'm not sure if they are worth the added complexity.

I can't formulate a specific question, but as this problem must have been solved thousands of times, I'd like to know if there is one preferred approach, well-known solution or reference design that can help me.

Benefits of separate ProviderOrders and ConsumerOrders bounded contexts:

  1. A single ConsumerOrder can generate multiple ProviderOrders (if the order contains products from multiple providers
  2. The workflow of a ProviderOrder might be different/more complex than the workflow of a ConsumerOrder.
  3. Both the consumer and the provider need to see their order history, which I envision as a denormalized table for fast reads, but both order histories contain different data (ie consumer orders contain provider information and provider orders contain consumer information) and are queried differently (by the consumer and by provider). This can be implemented in single table obviously, but it seems cleaner if they are 2 tables dedicated to a single purpose.
  4. Data isolation/partitioning. Consumer orders are always accessed by consumer Id, Provider Orders are always accessed by ProviderId.

I'm having a very interesting conversation about this topic on a separate forum, so I thought I should link it here, in case someone wants to read more thoughts on this topic.
Same question on NServiceBus discussion board

Note: This is implemented in .NET, by multiple teams, from multiple repositories and Visual Studio Solutions, hosted in a Service Fabric cluster and using NServiceBus for messaging.

Best Answer

The two-context approach reduces complexity by segregating your core into multiple contexts each specifically responsible for carrying out and enforcing rules. It also makes your model more expressive.

It is a common misconception that Domain objects should be based on some physical "thing" (i.e Customer, Order, Provider). We have to remember that the goal of DDD is to model the behavior of a system such that the resulting model represents useful abstractions based on the functional requirements and core business logic. And because the data of real-world objects rarely provides a good starting point for modelling functional requirements, domain objects are more likely to be named based on behavior in which they will engage rather than data/attributes they contain.

Many projects start with the physical model at the forefront of the modelling process which often leads to objects with too much responsibility and overlapping concerns (ultimately manifesting in your problem above with confusion over who "owns" what). Terms like Customer, Order, and Product (aside from not implying any behavior) tend to be too abstract, encompass too much knowledge, and therefore probably aren't very helpful in representing business logic.

With the above in mind, let's take a look at some possible domain objects we might find in a few different contexts:

Shopping

  • Shopper
  • ShoppingCart
  • CartItem
  • Coupon
  • Brand

Billing

  • Cashier
  • Buyer
  • Seller
  • PurchaseOrder
  • LineItem
  • PaymentMethod
  • BuyerInvoice
  • SalesReceipt
  • Currency
  • Money

Logistics

  • Manufacturer
  • Consumer
  • PurchaseRequest
  • Product
  • ProductType

Notice that this breaks down the "Customer" into more specific roles better suited to emphasize behavior (Shopper, Buyer, Consumer). Other contexts may partition a "Customer" even more (Visitor, User, Reviewer, etc). The same applies to "Order" (ShoppingCart, PurchaseOrder, PurchaseRequest), "OrderItem" (CartItem, LineItem, Product), and "Provider" (Brand, Seller, Manufacturer).

Although many of these objects will map to the same physical model, the data is broken down and managed according to different logical models. For example, we may want to know the IP address of Shoppers, the transaction history of Buyers, and the address of Consumers (or metadata like income for analysis).

I hope this points you in the right direction.

Related Topic