App Architecture.com

Understanding the modular monolith and its ideal use cases

By Priyank Gupta

Many monolith architectures are transitioning to microservices, often to address the maintenance and management challenges that accompany the need for highly-scalable apps and continuous development processes. However, microservices architectures come at a premium, as they impose much higher operational complexity than the traditional monolith.

It often seems like architects will have to decide whether they want to sacrifice simplicity of management to enable streamlined, modular development. However, a concept known as the modular monolith may provide development teams the perfect balance between these two extremes.

Let's examine what a modular monolith is and the underlying details of how it works. Then, we'll examine some scenarios that warrant the use of a modular monolith, and some that don't.

What is a modular monolith?

Conventional monolithic architectures focus on layering code horizontally across functional boundaries and dependencies, which inhibits their ability to separate into functional components. The modular monolith revisits this structure and configures it to combine the simplicity of single process communication with the freedom of componentization.

Unlike the traditional monolith, modular monoliths attempt to establish bounded context by segmenting code into individual feature modules. Each module exposes a programming interface definition to other modules. The altered definition can trigger its dependencies to change in turn.

Much of this rests on stable interface definitions. However, by limiting dependencies and isolating data store, the architecture establishes boundaries within the monolith that resemble the high cohesion and low coupling found in a microservices architecture. Development teams can start to parse functionality, but can do so without worrying about the management baggage tied to multiple runtimes and asynchronous communication.

Benefits and disadvantages of a modular monolith

One benefit of the modular monolith is that the logic encapsulation enables high reusability, while data remains consistent and communication patterns simple. It is easier to manage a modular monolith than tens or hundreds of microservices, which keeps underlying infrastructural complexity and operational costs low.

However, development teams must understand that modular monoliths don't provide all benefits of microservices, particularly when it comes to diversifying technology and language choices. Since the applications need to execute code within a single runtime, there is limited opportunity for mixing those runtime environments.

These types of polyglot technology stacks are useful when legacy technologies create a performance bottleneck or if developers want the ability to work and experiment with the language of their choice. In cases like these, the modular monolith will likely fall short when it comes to meeting your architecture goals.

What is the code structure of a modular monolith?

The right code design and layout are central to the modular monolith. Below, Figure 2 shows the code behind a movie ticket booking application that follows the modular model. The code has been structured with multiple feature modules and each one has an interface that represents its public definition. SeatMapService acts as the interface for the SeatMap feature. This interface is implemented by a class under the service package called SeatMapServiceImpl.

This code structure exposes the module interface in two ways:

  1. The module presents itself to external applications and services as an API, using protocols such as REST HTTP or GRPC. A reverse proxy, API gateway or other type of messaging broker can then manage these API calls, and do so among multiple modules simultaneously.
  2. Internal services and applications interact with the module via an abstracted interface component. This interface enables requesting applications to gather the information they need. However, they cannot access the actual implementation and must rely solely on the abstractions. This maintains a proper separation of concerns without compromising application processes.

Figure 3 diagrams the unidirectional dependency design, which is crucial for low-coupling in architectures. No matter how simple the application, architects must enforce a one-way dependency flow design. Since application code tends to grow over time, any multidirectional dependencies it possesses can eventually turn into troublesome dependency cycles. These circular dependencies will transitively affect other modules as well, making your entire infrastructure complex and rigid.

When do modular monoliths make sense?

Organizations shouldn't even consider microservices unless they have a system that's too complex to manage as a monolith, according to Kent Beck, creator of test-driven development and extreme programming. Architecture decisions should always revolve around the existing state of your environment, and the modular monolith strategy is no exception.

There are a few scenarios that are a natural fit for modular monoliths, such as:

But, like most architecture patterns, there are also scenarios where the modular monolith may produce diminished returns. These include:

Enforce consistency through tooling

In modular monolith systems, it helps to keep the code layout, access methods, and dependencies associated with each module relatively consistent. Typically, this is accomplished by building guiding conventions around compile-time and build-time processes.

There is plenty of tooling available to help build these required guardrails. SonarQube provides static code analysis highlight and prevents the proliferation of cross dependencies and code complexity over time. Tools like Checkstyle, JDepend and GitHub's CodeAssert are all designed to enforce a layout and access structure at build time using defined rules and configuration.

29 Oct 2020

All Rights Reserved, Copyright 2019 - 2024, TechTarget | Read our Privacy Statement