I’m really amused that softwareengineering.stackexchange.com has only two questions that are closely related to current topic. What’s even more absurd is their dauntingly low votes and views number, in spite of this subject is so strategically important. The code residing in system services might be perfect, but it doesn’t really matter if service boundaries are identified incorrectly, since there is no Business-IT alignment, that both DDD and SOA are striving for. And it means inevitable project failure.
Putting shortly, find the right metaphors. This is the same advice I follow when decompose domain into right objects. The metaphor that serves me well in both fields is a human metaphor. A human, who owns some knowledge, who can offer some service, who has an encapsulation barrier. After all, there is no much difference between objects and business-services, hence ways that can be applied to identifying service boundaries work well for objects, and vice-versa. Remember CRC cards? Doesn’t the “Responsibilities” part resemble you Business-capability mapping? If it does, you’re right on the money. Both these techniques serve to decompose your system from behavioral, object-thinking perspective.
Even if your business is something brand new, something that never existed and could not exist a hundred years ago, pretend it could. Conjure up with what it might look like. Presence of networks and computers often confuses and misleads when identifying service boundaries, hiding the real data owners, service responsibilities and their boundaries.
E-commerce nowadays doesn’t resemble a typical supermarket we all got used to. It’s more like a traditional store that my grandparents went shopping to. It looks like the following. You enter a shop and see a seller behind a counter. And you see an items at the shop-window in front of or behind a seller. Here is what it looked like:
A shop-window inside a shop
So, the first thing to note here, is that if some item was present in a shop-window, it didn’t mean you could buy it. Specific product’s stocks might have run out, and a store couldn’t sell you an item from a shop-window since God knows how long it was there. People 50 yeas ago were well aware of the eventual consistency concept. Or if a kid was asking for a bottle of wine, he would be rejected, naturally.So, the sellers responsibility was to validate and accept an order. As a result a seller gave you a bill with a a product type and its amount. For example, it could look like “Salmon — 500 g., Loaf of bread — 2 pc.”. And you took your way to that cashier. Cashier knew the prices, he or she was a single source of truth about it. Cashier looked like the following:
A cashier, accepting a payment for an order
As a result of her unit of work, she gave you a check, confirming that you really paid your order.
Then you came back to the seller, he or she made sure you paid the order, gave you your buyings and off you go.
The example above maps pretty straightforward to the e-commerce. Let’s take a simplistic look at what this process might look like in any e-shop, say, Amazon.As always, let’s consider the higher-level services as a sequence of steps to be taken to deliver business-value.
The goods are displayed on a web-site, so that they can be purchased and delivered. Then the customer wants to purchase some item. He selects delivery options and chooses a bank-card to be charged with. After that a system accepts his request and performs some internal validation. If everything’s OK, it means that an order was placed. Then a warehouse can start checking if every order item is present. And, finally, a customer can be charged. So, basically, there are four primary services:
Primary business-services in e-commerce
**_Sales_**Sales service accepts orders. After performing some internal validations, like, has this order already been placed, or can a current customer order certain products, an order becomes placed. This is, if I can say so, a business-event. For now, it doesn’t matter how it will be implemented in code. Right now it can be a phone call, an email or just a dialogue.
**_Warehouse_**It is the service that is interested in an event mentioned above, OrderPlaced. As soon as a warehouse accepts that event, stock levels of that product type are checked. Let’s imagine that Sales service is interested in the presence of every item of order, and if some product is absent for quite a long time, a customer is notified with “We’re sorry, the product you wish to purchase is absent”. That logic results in OrderItemReserved event published by Warehouse. Once again, right now I’m not thinking of events from technical point of view. It’s a business-event, reflecting the domain rules.
Both Sales and Warehouse logic might be more agile. For example, some order item might be absent in Warehouse, and it decides to wait until it’d appear in stock. But Sales can’t wait forever, it has some obligations to a customer, so it could have a time limit for how long it could wait for warehouse to reserve an order item. Talking about implementation, this would result in saga. It could be implemented with an Order aggregate. Warehouse would start its own sagas — one per order item. This logic, surely, would reflect domain requirements, which could be represented as following:
Sales and Warehouse sagas
If a customer accepts a partially accepted order, Warehouse’s reservation sagas for order items that are absent would be cancelled. Then you might think that a race condition could occur: exactly after an OrderWasPartiallyAccepted notification was sent to a customer, a missing order item could show up. Well, this situation could happen in any computer- and network-free business. It’s a scenario that must be considered from a business perspective, like all the rest. Most of the time, there is no such thing like race condition.
**_Billing and shipping_**When order is accepted, billing process starts. There can be multiple variations of this process, with its own sagas and events. It was considered in more detail in the first example. The process that includes shipping service as well can look like that:
Billing and Shipping services
After services are identified, we can consider which data belong to which services.
Product information except its price belongs to Sales. It’s what is operated upon by sellers, using the human metaphor and the example in the beginning of this post. And I keep in mind that my business-services include people and their communication with each other and with customers, and they heavily use product information as well. Besides that, Sales service needs to make some validation that includes a customer, so its data most probably would reside there either. For example, if validation includes an age check, then it definitely should reside there.
The warehouse doesn’t need product description or price, it’s interested in allowed storage conditions and tracking stock. So a concept of Product in a Warehouse bounded context would be quite different from the one in a Sales context.
All the data required to make a payment, like saved customer cards, product prices, currency courses, etc, should reside where it’s used — in Billing service. Again, the Customer entity in a Billing service would revolve around billing behaviors, which varies greatly from what is needed in Sales service.
Customer shipping details, apparently, should be owned by the Shipping service.
Well, there is no such service. As the name implies, it could have been needed for product display. But “product display” is too little to be some service’s responsibility. There is no logic here, no behavior. Backends for frontends is a more suitable choice for such things. This could look like that:
Composite view, comprised of data from various services
And I should warn you once again about pseudo-CQRS — data duplication between services. Data duplication is followed by functionality duplication, which is followed by common libraries or shared services. And this is the end of services autonomy. So if a product price appears the next line after a product name, it doesn’t mean they must belong to the same service.
Generally, UI is a poor helper in identifying data ownership and service boundaries. Data matters only when it’s involved in some business-logic, not when it’s just displayed.