freshidea - Fotolia
We can use different strategies to break down a platform into microservices. This process is known as service decomposition. A popular service decomposition strategy is decomposition by subdomains. Decomposition by subdomains is an approach that draws inspiration from the field of domain-driven design. DDD is an approach to software development that helps us to break down a system into loosely coupled components with well-encapsulated logic and clearly defined dependencies among them. When applied to the design of a microservices architecture, DDD helps us to provide a definition of the core responsibilities of each service and the boundaries that define the relationships among them. I would like to emphasize that the design of a microservices architecture does not necessarily have to mirror the results of our decomposition by subdomains according to DDD. DDD should play a guiding role in the design of our microservices, but it does not have to determine the architecture.
Domain-driven design background and basics
The methods of domain-driven design were best described by Eric Evans in his seminal book Domain-Driven Design (Addison-Wesley, 2003), otherwise called "the big blue book." DDD offers an approach to software development that tries to reflect as accurately as possible the ideas and the language that businesses or end users of the software use to refer to their processes and flows. In order to achieve this alignment with end users, DDD encourages developers to create a rigorous, model-based language that software developers can share with the end users. Such language must not have ambiguous meanings, and it is called "Evans Ubiquitous Language."
In order to create a ubiquitous language, we have to identify the core domain of a business, which corresponds with the main type of activity that an organization performs to generate value. For a logistics company, it can be the shipment of products around the world. For an e-commerce company, it can be the sale of products. For a social media platform, it can be feeding a user with relevant content. For a dating app, it can be matching users. In the case of the book's CoffeeMesh application, the core domain corresponds with the mission of the company to deliver the best coffee in the world to its customers, in the shortest possible amount of time.
The core domain is often not sufficient to cover all areas of activity in a business, so DDD also distinguishes supportive subdomains and generic subdomains. A supportive subdomain represents an area of the business that is not directly related to value generation, but it is fundamental to support it. For a logistics company, it can be providing customer support to the users shipping their products, leasing of equipment, managing partnerships with other businesses and so on. For an e-commerce company, it can be marketing, customer support, warehousing and so on.
The core domain gives you a definition of the problem space, which is the problem that you are trying to solve with software. The solution consists of a model, understood here as a system of abstractions that describes the domain and solves the problem. Ideally, there is only one generic model that can provide a solution space for the problem, with a clearly defined ubiquitous language. In practice, however, most problems are complex enough that they require the collaboration of different models, with their own ubiquitous languages. The process of defining such models is called strategic design.
Strategic design, step by step
How does this work in practice? How can we apply strategic design in order to decompose CoffeeMesh into subdomains? In order to break down a system into subdomains, it helps to think about the operations that said system has to perform in order to accomplish its goal. In the case of the CoffeeMesh application, we are interested in modeling the process from the moment the user hits the website to the moment the coffee is delivered into their hands. What does it take to accomplish this? Let's examine each step one by one. (See Figure 1 for an illustration.)
Step 1: When the user lands on the website, we first need to show a list of products together with their prices. Each product must be marked as available or unavailable, and must also come with an average user rating if reviews are available for that product. The user should be able to filter the list by availability, by price and by rating. The user should also be able to sort by price (from minimum to highest and from highest to minimum) and by rating (from minimum to highest and from highest to minimum).
Step 2: The user must be able to select a product and store it in a basket for later payment.
Step 3: The user must be able to pay for their selection.
Step 4: Once the user has paid, we must be able to pass on the details of the order to the kitchen.
Step 5: The kitchen picks up the order details and produces the product ordered by the user.
Step 6: The user must be able to monitor progress on their order.
Step 7: Once the user's order is ready for delivery, we must be able to arrange delivery with a drone.
Step 8: The user must be able to track the drone's itinerary until their order is delivered.
The first step requires a subdomain that is capable of providing a list of CoffeeMesh products on offer through an interface. We can call it the products subdomain. This subdomain must be able to determine which products are available and which are not. In order to do so, the products subdomain must contain logic capable of tracking the amount of each product and ingredient in stock. Other components of the CoffeeMesh platform can use such an interface to decrease the amounts in stock whenever they are used for production.
The second step requires a subdomain that allows users to make a selection of the items returned by the products subdomain and store them in a basket. This subdomain acts as an orchestration framework for the ordering process, and we can call it the orders subdomain. This subdomain owns data about the orders made by users, and it exposes an interface which allows us to manage orders and check their status. The orders subdomain serves as an API gateway, which hides the complexity of the platform from the user so that the latter doesn't have to know about all different endpoints and what to do with them. The orders subdomain also takes care of the second part of the fourth step: passing the details of the order to the kitchen once the payment has been successfully processed.
The third step requires a subdomain which can handle user payments. We will call it the payments subdomain. This domain must provide an interface through which the user can provide their payment details in order to process the payment. The payments subdomain owns data related to user payments, including user payment details, payment status and so on. It also contains specialized logic for payments processing, including card details validation, communication with third-party payment providers, different methods of payment and so on.
The fifth step requires a subdomain that knows how to interact with the kitchen to manage the production of the order made by the user. We call it the kitchen subdomain. The production system in the kitchen is fully automated, and the kitchen subdomain knows how to interface with the kitchen system in order to commission the production of a user order and how to track its progress. Once the order is produced, the kitchen subdomain arranges its delivery. The kitchen service owns data related to the production of the user order and exposes an interface that allows us to send orders to the kitchen and to keep track of their progress. The orders subdomain interfaces with the kitchen subdomain to update the status of the user order in order to meet the requirements for the sixth step.
The seventh step requires a subdomain that knows how to interface with the automated delivery system driven by drones. We call it the delivery subdomain. This subdomain contains specialized logic to resolve the geolocation of a user based on a given address, and to calculate the most optimal route to reach the user. It also knows how to manage the fleet of drones, and knows how to optimize the arrangement of deliveries. It owns data about the fleet of drones, and also about addresses and their coordinates, as well as data related to all the deliveries made by CoffeeMesh. The delivery subdomain exposes an interface which allows us to arrange a delivery and to keep track of it.
Service decomposition by subdomains
Through the strategic analysis of domain-driven design, we obtain a decomposition for CoffeeMesh into five different subdomains. These subdomains can be mapped to microservices, as each of them encapsulates a well-defined and clearly differentiated area of logic and owns its own data. None of these subdomains shows strong dependencies toward each other. These subdomains comply with the following good microservices design principles explained in the book, namely the database per service pattern, the principle of loose coupling and the single responsibility principle. The resulting microservices architecture is the following:
- Products subdomain > products service exposing its interface through the /products endpoint.
- Orders subdomain > orders service exposing its interface through the /orders endpoint.
- Payments subdomain > payments service exposing its interface through the /payments endpoint.
- Kitchen subdomain > kitchen service exposing its interface through the /kitchen endpoint.
- Delivery subdomain > delivery service exposing its interface through the /delivery endpoint.
As it turns out, the results of this analysis are very similar to the results that we obtained from the analysis of decomposition by business capability. Figure 3 shows the microservices architecture of CoffeeMesh resulting from the strategic design applied in this section.
Want to learn more about service decomposition by subdomains -- or the larger subject of developing microservices APIs with Python? Check out the book this section is extracted from by following the link at the top of this article.