Monolith or Microservices? The Debate Continues

Amazon Prime Video’s March 2023 announcement of migrating from distributed microservices to a monolith architecture surprised many in the global developer community, and reignited the Monolith versus Microservices debate. A varied range of analyses surfaced after that – some supported the move, some inferred that Amazon had a sub-optimal architecture in the first place, and other types of reports (e.g., this one) posited that the new architecture is not really a monolith.

The critical point here is not whether the microservices architecture is better than the monolith (or vice versa) but whether the selected architecture solves the target business problem efficiently and effectively. Like most decisions in business and life, there are tradeoffs to be considered. While the monolithic architecture is on one side of the spectrum, and microservices are on the other side, a new approach (called macroservices) falls in between. In this paper, I argue that a hybrid of macroservices and microservices is often most suited to building modern software applications.

Monoliths versus Microservices

Monolithic Architecture

In the conventional monolithic architecture, the entire application is built as a single unit. All the business concerns (services, layers, and components) are tightly coupled, and deployed together as a single build. While this architecture is easy to design and implement, it often suffers from certain limitations, such as:

  • all the services need to be deployed together, thus making the deployment process slow, and at times, even risky.
  • as the application grows, the codebase becomes unwieldy and complex, thus affecting extensibility and maintainability.
  • individual bugs in one or two modules can potentially bring down the entire application.
  • multiple developers working on a single codebase may create merge conflicts, and slow down development.
  • the entire application has to be scaled even if scaling is needed in only a certain part of the application.

Furthermore, monolithic applications tend to be deeply tied to the technology stacks they were initially built on – this often makes technology refreshes challenging, and at times, even practically infeasible without complete rewrites. It is important to note that modern monoliths are often modular and distributed – so, there’s a certain level of component decoupling inherent in them. However, the ‘high cohesion’ factor is still a cause of concern in most cases.

Microservices Architecture

In the microservices architecture, the application is built as a set of small, individual services. Each service is independently deployable, modeled around business domains, and generally manages its own data (or state).

The obvious benefits of microservices are efficient scaling (scale only those services that need scaling), faster deployments (individual services can be deployed quickly), improved fault tolerance (through strong failure isolation), and reuse of artifacts (e.g., different microservices can be combined in different ways to provide new functionality.) An additional benefit, particularly for building complex software or large enterprise applications, is technology heterogeneity. Some modern systems have a wide range of resource-intensive workloads, or continuously evolve over years – a combination of multiple technologies may serve better instead of adopting a ‘one-size-fits-all’ technology.

The microservices architecture is not without limitations. Firstly, in large applications, scores (or even hundreds) of individual microservices may communicate over the network. This not only causes latency challenges, but variations in latencies or loss of data packets are also observed at times, and these lead to unpredictability in system behavior. Secondly, each microservice’s infrastructure needs to be provisioned, deployed, and managed separately, thereby increasing costs and operational overheads. Thirdly, the integration of microservices is challenging, especially when complex workloads are dependent on the collaboration of multiple microservices. Since such integration testing is not always straightforward, it is not uncommon for integration issues to show up directly in production, thus affecting customer experience. Finally, as applications increase in complexity, debugging also tends to become challenging.

Neither Monoliths nor Microservices, on their own, may be able to optimally fulfill the needs of modern enterprise applications.

A Hybrid Approach: Combining Microservices with Macroservices

What are Macroservices?

The term ‘Macroservices’ has traditionally been used for VM-based services (as opposed to microservices that are generally container-based.) In modern understanding, a macroservice is a kind of super-service where a group of close-knit individual business services (that need to have tight coupling) are connected together, designed to share the same database, and deployed as a single package. Each macroservice is independently scalable and deployable with lesser operational complexity and inter-service communication overhead than microservices.

Strategy 1: Macroservices + Microservices

Most enterprise software do not need the extreme granularity of microservices for everything that it does. It is not just about developing something that can hyper-scale, but also about the maintainability and extensibility of that software in the years to come. Moreover, many enterprise applications have use cases where functionality needs to be closely knit. Such systems can be optimally developed as a hybrid architecture comprising a handful of macroservices, and tens of microservices. While each macroservice delivers close-knit functionality as a unit, the microservices drive workloads that need intense scaling, or may consist of long-running tasks.

Strategy 2: Macroservices + Microservices + Monoliths

For enterprise applications that were traditionally built as monoliths, there is a tendency to completely migrate them to microservices. This is sometimes more about following trends than serving actual purposes. Not only is this costly and hugely disruptive for engineering operations, but the law of diminishing marginal utility also kicks in at some point. A better approach might be to migrate only those services that truly need scaling (to microservices), build new features as a hybrid of microservices and macroservices, and continue with the remaining code bases as monoliths.

Closing Comments

While a combination of macroservices and microservices may be suitable for engineering modern applications, it does not necessarily mean that companies should change their existing monolithic structures without a genuine need. Furthermore, if a transformation indeed happens, the key is to keep things simple. For instance, it is advisable to stick to standard design patterns instead of chasing after fads. It is also important to resist the temptation of combining this transformation with major product changes. The goal is really to transform the existing application into one that can be sustained over the next several years.

Acknowledgement:

Building Microservices, Sam Newman, 2022.

Share this article.