Microservice architecture is a powerful approach to building modern distributed systems, which is based on breaking down a monolithic application into smaller, more manageable microservices. And decomposition patterns are used as ready-to-use strategies to define microservice boundaries for the future system. There is a decomposition by transaction, a decomposition by business capability, and a decomposition by subdomain.
A developer or an architect should carefully consider the specifics of their project and choose the correct pattern that best suits their needs to ensure a successful implementation of microservices because choosing the wrong strategy can lead to significant challenges and issues.
Way back in August 2008, Netflix had a significant outage because of a major database corruption which prevented them from shipping DVDs to customers for three days. Following this, they decided to move away from a single point of failure–that could only scale vertically–and move to components that could scale horizontally and are highly available [1].
What Is This Article About?
I am excited to share a series of articles on Microservice Architecture Patterns. In this series, we will explore various categories of microservice architecture patterns, including decomposition, integration, database, observability, cross-cutting concern microservice architecture patterns, and others.
In this first article, we will focus on decomposition patterns. These patterns help to break down a monolithic application into smaller, more manageable microservices. In detail, we will discuss three decomposition patterns: microservices decomposition by business capability, microservices decomposition by subdomain, and microservices decomposition by transaction. By the end of this article, you will better understand how to use these patterns to design and implement microservice architecture in your projects.
What Is Microservice Architecture?
Microservices are an architectural and organizational approach to software development where software is composed of small independent services that communicate over well-defined APIs [2]. Basically, microservice architecture approach has become popular in building software systems, especially in modern web applications.
Main Benefits of Microservice Architecture
Scalability: One of the most meaningful advantages of microservice architecture is its scalability. Each microservice can be scaled independently, allowing the application to handle a more significant load without affecting the performance of other services.
Flexibility: Microservices are highly decoupled, meaning each service can be developed, deployed, and updated independently of other services. Furthermore, high decoupling makes introducing new features and updating existing ones easier without affecting the entire system.
Resilience: Microservices are designed to be fault-tolerant (where fault tolerance is achieved through redundancy and automatic failover mechanisms), which means that if one service fails, it won't bring down the entire system.
Technology Heterogeneity: Microservices architecture allows you to use different technologies for each service, so you can choose the best technology for the specific task or service. It can lead to better performance, scalability, and flexibility.
Continuous Delivery: Microservices architecture supports continuous delivery, allowing you to release updates and new features faster and more frequently without affecting the entire system.
However, There Are Some Challenges in This Approach
While microservices offer many benefits, they also come with some challenges:
Service boundaries: Identifying and defining the boundaries of each microservice can be challenging, as there may be different ways to slice the application into microservices, and getting the boundaries right is critical for the success of the system.
Service Discovery: One of the main challenges of microservice architecture is service discovery, which refers to the process of finding and communicating with other services. As the number of services grows, keeping track of all the services and their locations can be complex.
Data Consistency: Microservices architecture can lead to data consistency issues as each service manages its data. Maintaining consistency across multiple services can be challenging, and you must ensure that data is consistent and up-to-date across all services.
Security: Microservices architecture can introduce additional security challenges, as you need to secure each service individually. It includes authentication, authorization, and secure communication between them.
Transaction Management: Transactions spanning multiple services can be challenging to manage in microservice architecture, as each service manages its data and transactions. Ensuring that transactions are consistent and recoverable across various services can be difficult.
Testing: Testing microservices can be problematic. You need to ensure that you can test each service independently and that you can test the overall system to ensure that it functions correctly.
How Can We Overcome These Complex Challenges?
To address these challenges, software developers and architects have developed a variety of software architectural patterns that can help to overcome microservice architecture drawbacks and disadvantages.
Using patterns, you can avoid reinventing the wheel and leverage proven solutions to common problems when designing and building software systems. In the context of microservice architecture, patterns can help address some of the challenges that arise when dealing with many independent services and can reduce complexity, increase reliability, and improve the system's maintainability.
Decomposition Patterns
As mentioned above, there are many different categories of microservice architecture patterns, but without one category of patterns, there would be no microservice architecture. This category is microservices decomposition patterns, which enables splitting the application into a set of loosely coupled services. There are three primary decomposition approaches, and we can learn all of them further. Let's start!
1. Decomposition by Business Capability
Description
This approach is based on business capability decomposition, which aims to create microservices aligned with the business needs and can be developed and maintained independently. Business capabilities are the specific functions or processes that an organization performs to deliver value to its customers [3]. It is a high-level concept that describes what a business does rather than how it does it. For example, a banking application might have business capabilities for account management, transaction processing, and loan origination. Each of these capabilities could be supported by a separate microservice.
This Pattern Is Better to Use If:
Here are some situations in which business capability decomposition may be a good fit:
Your team has enough insight into your organization's business units, and you have subject matter experts (SMEs) for each business unit [4].
An application has a large number of interrelated functions or processes, or when those functions or processes are likely to change frequently. By breaking down the application into smaller, more focused services, development teams can iterate and experiment more quickly and easily and respond more effectively to changing business requirements and market conditions.
Suppose your organization has multiple business units or departments with distinct areas of functionality and workflows. In that case, business capability decomposition can help to align your application architecture with these capabilities, making it easier to develop and deploy new features and services.
Advantages
Business Alignment: By organizing microservices around business capabilities, aligning software development with business goals and objectives becomes easier. As a result, it also makes it easier to prioritize development efforts and make sure that the most critical business capabilities are addressed first [5].
Cross-Functional Teams: This pattern encourages cross-functional teams that are focused on specific business capabilities. And this, in turn, can help break down silos between different departments and ensure that everyone is working towards a common goal.
Improved User Experience: By focusing on specific business capabilities, microservices can be optimized for the particular needs of end-users. It can result in better user experiences and higher user satisfaction.
Let's See an Example
Let's consider a hypothetical healthcare platform that provides various services such as patient management, appointment scheduling, electronic medical records (EMR) management, billing and payment processing, and telemedicine services. To decompose these business capabilities into microservices, we can assign one distinct business capability and its data to a separate microservice, which can be developed, deployed, and scaled independently.
Here is an architectural diagram that illustrates this decomposition.
In Picture 1, we can see how the healthcare platform can be decomposed into microservices based on business capabilities:
Patient Management: This microservice manages patient records, such as patient demographics, medical history, and insurance information.
Appointment Scheduling: This microservice is responsible for scheduling patient appointments, sending patient reminders, and managing the appointment booking process.
Electronic Medical Records (EMR) Management: This microservice manages patient medical records electronically, including lab results, imaging studies, and other medical data.
Billing and Payment Processing: This microservice generates patient invoices, processes payments, and manages insurance claims.
Telemedicine: This microservice provides patients with remote consultations and telemedicine services, allowing them to consult with doctors remotely.
We can achieve better scalability, flexibility, and maintainability by decomposing the healthcare platform into microservices based on business capabilities. For example, we can scale up the Telemedicine microservice during a pandemic or other situations where remote consultations are in high demand without affecting the performance of other services.
Possible Challenges and Issues
Difficulty in identifying business capabilities: Identifying and defining the appropriate business capabilities to use as the basis for microservices with the proper boundaries can be a complex and time-consuming process.
Tight coupling between microservices: If business capabilities are not clearly defined or there is an overlap, there may be a high degree of coupling between microservices. A high degree of microservices coupling can make modifying or replacing individual ones difficult without affecting the overall system, increasing the risk of downtime or other issues.
Complexity in coordinating microservices: Coordinating the various microservices involved in a business capability can be complex, especially if the business capability involves multiple steps or interactions with external systems.
2. Decomposition by Transaction
Description
One popular approach to decomposing microservices is through transaction decomposition, where the system is designed such that each particular transaction belongs to a separate microservice. A transaction, in this context, refers to a series of operations performed between components of the system as a unit of work to achieve a particular business goal.
In this pattern, the components that participate in a transaction are grouped together as part of the same microservice. This grouping helps to avoid problems with two-phase commit and reduces latency issues that may arise in a distributed system. By adopting this approach, organizations can achieve a more efficient and scalable microservice architecture that is better suited to their specific business needs [6].
This Pattern Is Better to Use If:
This pattern can be reasonable in the following scenarios:
When there are transactional operations that involve multiple components or services.
When there are frequent and complex transactions, such as in e-commerce applications, financial systems, and other transaction-intensive environments.
When there are strict consistency requirements, where it is important to ensure that data is always in a consistent state across all components involved in a transaction.
Advantages
Reduced complexity: By grouping the components that participate in a transaction together into the same microservice, this pattern can help reduce the complexity of coordinating transactions across multiple services. Then it can make developing, testing, and maintaining the system easier.
Minimized two-phase commit problems: With this pattern, each transaction belongs to a separate microservice, which can help to avoid issues with two-phase commit. Then it can lead to a more reliable and fault-tolerant system.
Reduced latency: By grouping components together in the same microservice, this pattern can reduce the latency that can arise from coordinating transactions across multiple services. Then it can lead to faster response times and a better user experience.
Better alignment with business needs: By decomposing the system based on transactions, this pattern can help to better align microservice architecture with the organization's specific business needs. Then it can lead to a more efficient and effective system.
Let’s See An Example
Let's consider an e-commerce application that allows customers to purchase products online. To implement this application using microservice architecture, we could use the transaction decomposition pattern to break down the system into smaller, more manageable microservices that each handle a particular transaction.
Here is an architectural diagram that illustrates this decomposition.
In Picture 2, we can see how the e-commerce platform can be decomposed by transaction into microservices that could be part of this system:
Shipping microservice: This microservice would handle the transaction that involves shipping the order to the customer. It would handle tasks such as generating shipping labels, updating tracking information, and communicating with the carrier.
Product catalog microservice: This microservice would handle the transaction that displays product information to the customer. It would handle tasks such as retrieving product information from a database, caching product data, and providing search and filtering functionality.
Payment processing microservice: This microservice would handle the transaction that involves processing the customer's payment. It would handle tasks such as verifying the customer's payment information, communicating with the payment gateway, and updating the order status.
Order management microservice: This microservice would handle the transaction that involves creating and processing an order. It would handle tasks such as validating the customer's payment information, updating inventory, and sending a confirmation email to the customer.
By breaking down the system into these smaller microservices, we can achieve a more scalable, fault-tolerant, and efficient system that can better handle the demands of a high-traffic e-commerce website. Each microservice is responsible for a specific transaction, which helps to minimize complexity and improve performance.
Possible Challenges and Issues
Potential for monolith creation: When using the transaction decomposition pattern, it's possible to package multiple modules together into a single microservice. It, in turn, can create a monolithic architecture, which goes against the principles of microservices. To avoid this, it's important to keep each microservice focused on a single business domain [7].
Increased cost and complexity: If a single microservice handles multiple functionalities instead of separating them into separate microservices, the cost and complexity of that microservice can increase. It, in turn, can lead to longer development times and higher operational costs. It's essential to carefully consider the scope of each microservice to avoid this.
Incompatible versions: When deploying microservices, it's possible to accidentally deploy different versions of the same microservice for the same business domain. It, in turn, can lead to inconsistent behavior and potential errors in the system. To avoid this, it's important to carefully manage versioning and deployment processes.
3. Decomposition by Subdomain
Description
Microservice decomposition by subdomain is a process of breaking down a monolithic system into smaller, independent microservices based on the corresponding subdomains defined by Domain-Driven Design (DDD). DDD refers to the application's problem space - the business - as the domain, which is comprised of multiple subdomains [8]. The scope of each subdomain's model is called a bounded context, around which microservices are developed. This pattern is suitable for monolithic systems that have clear boundaries between subdomains and allows the repackaging of existing modules as microservices without extensive code rewriting. By breaking down the monolithic system into microservices based on subdomains, each microservice has its own model, which enables greater agility, scalability, and maintainability.
This Pattern Is Better to Use If:
Microservices decomposition by subdomain is a software design pattern that is well-suited for applications that have complex business logic and are expected to evolve over time. This approach to software design enables developers to break down a large, monolithic application into smaller, independent services that are focused on specific areas of business functionality.
Here are some scenarios where this pattern is well-suited:
Large, complex applications: Microservices decomposition by subdomain is particularly useful for applications with a large amount of business logic, with multiple workflows, data models, and interdependent rules. Breaking down the application into subdomains makes it easier to manage and maintain the codebase while enabling faster development and deployment.
Agile development: Microservice architecture is well-suited for agile development methodologies, as it enables developers to work on smaller, independent services that can be developed and deployed more quickly. This approach can improve time-to-market, reduce development costs, and improve the overall quality of the software.
Scalability: By breaking down an application into smaller, independent services, it becomes easier to scale each service independently of the others. This approach can improve the overall scalability and performance of the application, as each service can be optimized and scaled as needed.
Flexibility: Microservices decomposition by subdomain enables developers to make changes to specific areas of functionality without affecting other parts of the application. This approach can improve the overall flexibility and agility of the application, enabling it to adapt more quickly to changing business requirements.
Advantages
Improved scalability and predictability: This pattern enables better scalability and predictability by breaking down the monolithic system into smaller, independent microservices. Microservices can be scaled up or down as needed, and faults or failures in one microservice will not affect the rest of the system. It can result in a more stable and reliable system that can handle changes in demand and usage patterns [9].
Loosely coupled architecture: This pattern provides a loosely coupled architecture, meaning that microservices are designed to operate independently from one another. It, in turn, offers many benefits, including:
Scalability: Individual microservices can be scaled up or down independently, providing greater flexibility and cost-effectiveness.
Resilience: Faults or failures in one microservice will not affect the rest of the system, reducing the risk of downtime or data loss.
Maintainability: Changes or updates can be made to individual microservices without affecting the rest of the system, making maintenance and updates simpler and faster.
Extensibility: New microservices can be added or removed without affecting the rest of the system, providing greater flexibility for future development.
Location transparency: Microservices can be deployed in different locations, making it easier to deploy services closer to users or data sources.
Protocol independence: Microservices can communicate using different protocols, making it easier to integrate with existing systems.
Time independence: Microservices can be developed and deployed independently, allowing different teams to work on different services without affecting each other.
Let’s See An Example
Let's consider an insurance monolith application that allows clients to purchase insurance online. To implement this application using microservice architecture, we could use the subdomain decomposition pattern to break down the system into smaller, more manageable microservices that each belong to a particular subdomain. Here is an architectural diagram that illustrates this decomposition [10].
In Picture 3, we can see how the insurance monolith application was decomposed by business capabilities into four services: sales, customer, compliance, and marketing. Then the sales service was further divided into purchasing and claims subdomains, while the marketing subdomain was divided into campaigns, analytics, and reports subdomains.
Here's a brief overview of microservices that were created in each subdomain:
Purchasing service: handles the purchase of insurance policies
Claims service: handles the processing of insurance claims
Customer service: manages customer information and interactions
Compliance service: ensures regulatory compliance and manages risk
Campaigns service: manages marketing campaigns
Analytics service: tracks and analyzes marketing data
Reports service: generates reports on marketing performance
By decomposing this monolith into microservices based on subdomains, the company was able to improve its agility, scalability, and time to market. They could independently develop, test, and deploy each microservice, allowing them to innovate faster and respond quickly to changing market demands. Additionally, they improved the reliability and availability of their services, as failures in one microservice did not affect the entire system.
Possible Challenges and Issues
Potential for creating excessive microservices: This pattern can result in the creation of a large number of microservices, which can make service discovery (the process of finding and connecting to available services) and integration (combining these services to form a functional system) challenging. As a result, it leads to increased complexity and potential operational issues.
Difficult to identify business subdomains: This pattern requires a thorough understanding of the overall business to determine the appropriate subdomains. Without sufficient knowledge of the business, it can be challenging to identify the most appropriate subdomains, potentially resulting in suboptimal microservice design and implementation.
Conclusion
In conclusion, microservice architecture is a powerful approach to building modern distributed systems, and decomposition patterns provide valuable strategies for breaking down a monolithic application into smaller, more manageable microservices. By using patterns such as decomposition by transaction, decomposition by subdomain, or decomposition by business capability, teams can create a more flexible, scalable, and maintainable architecture. However, it is important to note that each pattern has its advantages and disadvantages, and choosing the wrong strategy can lead to significant challenges and issues. Therefore, readers should carefully consider the specifics of their project and choose the correct pattern that best suits their needs to ensure a successful implementation of microservices.
About the author
Zufar Sunagatov is an experienced senior software engineer passionate about designing modern software systems. He strongly advocates microservice architecture and has worked extensively with complex distributed systems using various design patterns in big e-commerce, fintech, and insurance companies. Zufar is an active member of the software development community and regularly shares his knowledge and expertise on his Telegram channel and where he explains algorithm and system design task solutions and approaches that have been well-received by the community.