6

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.

  • What are the "several benefits" that you see? – Robert Harvey Feb 19 '18 at 18:20
  • How do you envision the providers interact with your architecture? I would imagine that each provider would also maintain their orders. With that you have multiple places to find orders: the consumer, your application, and the providers. – Erik Eidt Feb 19 '18 at 18:25
  • @RobertHarvey see my update. – Francesc Castells Feb 20 '18 at 08:16
  • @ErikEidt both providers and consumers will send commands to alter the order (PlaceOrder, AcceptOrder, CancelOrder, etc) and they'll do queries to view order history and order details. See update 1 point 3 regarding order history. – Francesc Castells Feb 20 '18 at 08:18
  • there are a few standard for tendering and general business. but its going to be painful https://en.m.wikipedia.org/wiki/EbXML – Ewan Feb 20 '18 at 13:59
  • Seems like a pretty solid list of benefits. Can you think of any reason you wouldn't want to implement them? – Robert Harvey Feb 20 '18 at 15:36
  • 1
    @RobertHarvey probably just fear of introducing too much complexity: It means both types of orders will be synchronised by events. They'd be nicely decoupled, but it makes it hard to follow what is going on just looking at the code as you need to track all event subscribers. Potential event versioning issues. Need of monitoring the queues' lengths, etc. – Francesc Castells Feb 20 '18 at 16:53
  • 2
    Yes. This is exactly the sort of analysis you should be doing. Now you just have to decide. :) It's not about the "standard" way of doing it; it's about which approach is best for your specific situation, given the pros and cons. – Robert Harvey Feb 20 '18 at 17:00
  • Thanks @RobertHarvey. It's true and it helps a lot to talk about it. Every time I try to describe it, I discover new clues that lead me to one side or the other. – Francesc Castells Feb 21 '18 at 08:43

3 Answers3

10

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.

user3347715
  • 3,084
  • 11
  • 16
  • This is another awesome answer! So how do you deal with a shopper checking out and then becoming a buyer if they're in different BCs. Basically what I'm getting at is BC inter-communication. – keelerjr12 Dec 04 '18 at 22:45
  • Generally speaking, inter-communication is done via integration events (application-layer). Your specific question doesn't really require communicate of this sort. A `Shopper` needn't *become* a `Buyer`, a `Shopper` is *already* `Buyer`. These two concepts are not distinguished by their identity (they may share an `id`), rather their behavior. Checking out requires reading the `ShoppingCart` from the `Shopper`'s session (assuming web-based), and passing the `shopperId` and `products` to the appropriate command. The Shopping context doesn't need to talk to the Ordering context directly. – user3347715 Dec 05 '18 at 16:55
  • WRT to DDD we hardly ever speak of the persistence model; however, would a shopper AND buyer map to the same table for some properties? Or would they be completely distinct. The concepts I understand; it's the implementation details that escape me. – keelerjr12 Dec 05 '18 at 17:03
  • @keelerjr12 How these entities map to a data store is indeed an implementation detail. For example, the Shopping context in it's simplest form might not even exist in our domain, rather, it might exist in our UI and persisted to a session on a web server. Of course in the real world load balancers exist and/or our business wants to collect user data. This may require an RDBMS solution (and the movement of this context into our domain). At the very least I would expect a `Shopper` to somehow relate to a `Person`. This means, at a minimum, we will have a FK. So completely distinct? No. – user3347715 Dec 05 '18 at 18:42
  • Awesome! Final question, where would the mapping happen to go from `CartItem` to `LineItem` in a `PurchaseOrder`? For example, when the `Buyer.PlacesOrder(List items)`? I obviously need to map the Shopping Context's `ShoppingCart`'s `CartItem`s to `LineItem`s? Would this be the Translation Layer or Anti-Corruption Layer that maps between different BCs? – keelerjr12 Dec 05 '18 at 18:54
  • @keelerjr12 There is more than one way to accomplish this. In terms of data flow, let's visualize how data moves around: `[Visitor navigates to your site] -> [ShoppingCart (session) is populated from database] -> [Visitor adds more CartIems to ShoppingCart] -> [Visitor clicks "purchase"]`. At this point our UI knows the `productId`s necessary to compose the command and can send them directly. – user3347715 Dec 05 '18 at 19:34
  • @keelerjr12 If instead we want to have a flow where we save our `ShoppingCart` and *then* retrieve it's contents server-side upon issuing a `PurchaseOrderCommand` we need a new concept (VO) in our Ordering context to hold that data *before* it can be part of an `Order`. We may have something like: `purchaseRequest = prs.Find( shoppingCartId ); buyer.PlaceOrder( purchaseRequest );`. In this way our Shopping context builds a `ShoppingCart` which then maps to `PurchaseRequest` in our Ordering context. An `Order` is now comprised of a `BuyerInfoSnapshot` and `PurchaseRequest`. – user3347715 Dec 05 '18 at 19:41
  • @keelerjr12 Importantly, a `PurchaseRequest` may hold additional data like `CouponCode`. The choice between the two variants (and possibly others) is for you to decide based on your own technical considerations and business rules. – user3347715 Dec 05 '18 at 19:43
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/86678/discussion-between-keelerjr12-and-king-side-slide). – keelerjr12 Dec 05 '18 at 19:56
2

I assume you are using a microservices architecture to decouple your application into independently operating/scaling/developable/deployable parts.

It seems to me, that "ProviderOrders" and "ConsumerOrders" are not independent in this sense. They are interdependent. They need to communicate in both directions, they need eachother to fulfill their own responsibilities, they have a circular dependency. Eventing would cover this up to some extent, but it's still there.

So this would be an argument against option 2.

Now, you can still keep both concepts and all the benefits of that, and put it into a single service. Let's call that version 1B. You eliminate the conceptual circular dependency and the communication overhead.

Robert Bräutigam
  • 11,473
  • 1
  • 17
  • 36
  • Yes, this circular dependency exists. Lately I am thinking that even if both concepts exist, they wouldn't be in two different Bounded Contexts, but both would belong to a single Sales BC, which I think it's what you propose. Now, if I avoid events and eventual consistency, I go to the other side: transactionability. Theoretically, I shouldn't modify 2 different aggregates (both types of orders) in a single transaction, but in practice, that could avoid weird scenarios where one modification works and the other fails. Or would you put both concepts in the same aggregate? – Francesc Castells Feb 21 '18 at 14:42
  • I don't know enough about your use-case to make that decision. I would suggest you forget the technicalities for a moment, and list all the (business) functionality you want to cover. Assign those to concepts you and the business share. In other words do OO design. If you do that right (admittedly not always easy) you will get methods for every business use-case. I.e. methods that will be exactly one transaction. You are trying to do it the other way around, trying to guess in advance what *should* be an aggregate. – Robert Bräutigam Feb 21 '18 at 15:59
  • Sure, I have many concepts, use cases and ideas in my head and it's time to start formalising them into more specific requirements and solutions. Thanks. – Francesc Castells Feb 21 '18 at 16:08
1

I think it's a concierge service with multi vendor. You won't be able to come to conclusion in this way.

There are many dependencies among the Consumers and Providers. Actually no provider owns an order because an order may logically contain many providers' products but physically requests a single particular product, i.e. an order from a consumer may have 3 products from 3 different providers. So each provider gets a single order from that consumer's order.

You need to analyse the order physically rather from the point of a consumer.

Mael
  • 2,305
  • 1
  • 13
  • 26
kazi
  • 11
  • 2
  • Not sure if I follow 100%, but it seems you are in favour of my option number 2. Having separate ProviderOrder and ConsumerOrder, where a single ConsumerOrder might relate to several ProviderOrders, right? – Francesc Castells Feb 21 '18 at 08:32
  • Correct. A ConsumerOrder have many ProviderOrder. – kazi Feb 22 '18 at 08:40