Chief Technical Writer
Jan 8, 2024 | 6 mins read
Microservices-based architectures emerged together with the cloud-native software development paradigm, bringing numerous advantages over traditional monolithic architectures, the most significant being that they excel in creating scalable, resilient, and agile applications that are well-suited to the dynamic and evolving nature of cloud-native ecosystems. Statistics from Stack Overflow in its 2023 developer survey describe how 49% of software developers now use microservices when developing software.
However, even though a microservices-based application is a direct juxtaposition to a monolithic software application, it can also start resembling a monolith over time.
How is this possible?
Succinctly stated, because of less-than-ideal development practices when developing a textbook microservices-based architecture, it will gradually evolve into a monolithic-style architecture.
Note: This phenomenon is also referred to as a distributed monolith.
At this juncture, the question that begs is: How do we prevent microservices-based software from turning into monolithic-style applications?
Let’s start answering this question by first examining several software development practices or architectural decisions that change a microservices-based application into a distributed monolith.
One of the core principles of a microservices-based architecture is to develop loosely coupled services that operate independently. However, these microservices can become tightly coupled if they become too interdependent, resulting in an architecture that resembles a monolith despite being physically separate.
Additionally, microservices should ideally have exclusive access to their own data source. When microservices frequently access a shared data source, it can create dependencies that mimic a monolith.
Excessive inter-service communication over a network can introduce bottlenecks and high latency, similar to the challenges faced in a monolithic architecture, where tightly integrated modules will likely slow down the application’s operability.
The microservices-based architecture deeply embeds the ability to build a technology stack using diverse technologies, tools, and frameworks, including multiple programming languages, hybrid computing environments, as well as different platforms and devices—desktop and mobile devices. However, over-regulation, over-governance, and management complexities can result in a rigid system that is as difficult to manage as a monolithic application.
Lastly, poorly defined boundaries between individual microservices can result in functionality duplication, redundancy, and a bloated architecture that is overly complex, not unlike an ERP’s monolithic framework.
The solution to inadvertently creating distributed monoliths is to embed the principles of composability—or composable microservices—into your microservices-based architecture. This concept is an evolutionary methodology or approach to designing and managing microservices-based applications, addressing some of the challenges facing software developers.
But first, what is composability?
The phrase “composable technology” describes a “modular, responsive, and open-form architecture.”
Based on this definition, we can define composability (in software development/engineering) as a design principle that informs the ability to combine and recombine microservices to create different applications or systems. In a composable architecture, individual components or modules are designed to be used in various contexts and configurations, ensuring a microservices-based application does not evolve into a distributed monolith by emphasizing flexibility, scalability, and ease of maintainability.
Before we dive into a practical example, let’s look at several key features of composability:
As elucidated in the name, a microservices-based architecture is developed using any number of distinct modules or components, each responsible for a specific functionality. The salient point is that these microservices (or parts) can be developed, tested, and deployed to production independently.
Composable microservices are designed to work together seamlessly—even though they are loosely coupled (as described below). They can communicate and interact with each other easily through well-defined APIs.
These microservices are loosely coupled. This principle is the antithesis of tightly bound microservices. Thus, they interact with each other through abstract interfaces—or APIs. As a result, you can make changes to—or replace—a microservice without impacting the application’s functionality.
Composability enables agile, flexible, and scalable systems. New microservices can be added or existing microservices updated without having to rework the system in its entirety. The same principle applies to scalability: individual microservices can be scaled on demand.
The last section of this article describes how to tackle a real-world problem, including the need for scalability on a massive level, dynamic capabilities, global scope, hybridism, and real-time access to the application and its accompanying data. If composability is not baked into the microservices-based application architecture, not only is it at risk of devolving into a distributed monolith, but these requirements cannot be reliably and sustainably met.
Imagine a global eCommerce platform that must include the capacity to handle millions of users, manage an exhaustive inventory, process an exceptionally high volume of transactions, and provide personalized user experiences.
To reiterate, the mandatory requirements also include:
For the sake of brevity—and simplicity, let’s decompose this application into the following five microservices:
Note: In practice, these services must be decomposed into several smaller microservices, and a complete eCommerce system will include many more services.
The functionality of each microservice is self-explanatory, so let’s dive into how composability fits into this architecture and how it provides for all the requirements—including the mandatory requirements—elucidated above.
At this point in this discussion, it is important to note that composability fits into this eCommerce platform’s architecture by ensuring that each microservice is designed as a self-contained unit with specific functionalities, which can be combined or recombined to meet different requirements. In a sense, composability embraces software development best practices—or rules—as described in “The Art of Unix Programming” by Eric Raymond, the most crucial being developing simple, atomic, and reusable modular programs.
Embedding composability into your microservices-based applications brings the following benefits:
In this design pattern, each microservice is a standalone module that handles a specific feature of the eCommerce shopping process—as seen in the microservices listed above. These microservices modules can be developed, tested, and deployed independently, reducing the complexity of managing the whole system.
Composability ensures that the microservices-based architecture can quickly adapt to evolving business requirements. For example, suppose there is a requirement to update the shopping cart microservice to comply with new security and privacy regulations. In that case, this service can be updated or replaced without affecting the system’s functionality. In the same way, new microservices can be added to the eCommerce platform without rearchitecting the system.
Common microservices, like user authentication and logistics services, can be developed as reusable components, reducing redundancy and accelerating the development of new microservices. The salient point is that reusability also ensures consistency across different parts of the application.
Microservices-based applications comprise any number of microservices packaged inside containers and orchestrated by a container orchestration tool like Kubernetes. This provides the foundation for composability, allowing for scaling individual microservices independently based on the application’s load and performance requirements. For instance, the order processing and shopping cart services can scale up to handle increased demand during events like Black Friday and Cyber Monday.
The nature and architecture of the composable microservices facilitate integrating different cloud services as well as fog (or edge) computing and on-premises servers. Therefore, each microservice can be hosted in the most appropriate environment. For example, the microservices that collect—or process—Personable Identifiable Information can be hosted on servers—in the cloud or on-premises—to comply with regulations like the GDPR. Secondly, logistics and supply chain tracking microservices can be hosted on IoT devices located on shipping containers or warehouses along the supply chain route.
The composable architecture simplifies and mitigates the risk of updating or maintaining individual microservices. Changes in one service have minimal impact on the rest, reducing downtime and improving system stability. It also allows CI/CD—Continuous Integration, Continuous Deployment—best practices to be implemented successfully.
Composable microservices can independently process real-time data streams from tools like Apache Kafka, enabling dynamic and real-time responses critical for inventory updates, shipping progress notifications, pricing adjustments, and personalized user experiences.
In modern software development, composability is the cornerstone that allows microservices-based application architectures to be dynamic, scalable, and maintainable. Composable microservices allow for the development of software applications, from small applications suiting small startups to complex ERP systems consisting of a few to many containerized microservices. And, as described above, these microservices can be used and reused in different scenarios, adopting the principle of “code once, use many times.”
Without composability, the architecture risks becoming a distributed monolith, where changes to one microservice necessitate changes to many other—if not all—microservices. DevOps teams would then struggle to adapt quickly to new requirements, slowing down the development process and reducing the application’s agility as well as its ability to scale efficiently in response to fluctuating demands, which are imperative in a global context.