Developing Modular Monolith vs. Traditional Monolith in Software Engineering, pros and cons
Tags: modularmonolith, monolith, microservices, softwareengineering
Published: 2023-06-22 13:07:05

Monolithic architecture has been a widely adopted approach for developing complex applications. However, as technology evolves, engineers have explored alternative architectures to address scalability, maintainability, and deployment challenges. Two prominent approaches are developing a modular monolith and a traditional monolith. In this article, we will examine the pros and cons of each approach to help software engineers make informed decisions when choosing between them.

**Understanding Traditional Monolith**:
A traditional monolithic architecture is characterised by an application composed of tightly-coupled modules that are deployed and scaled as a single unit. All components, such as the user interface, business logic, and data access layer, reside within the same codebase and are developed, tested, and deployed together. While this approach simplifies development and deployment, it can lead to challenges as the application grows larger and more complex.

**Understanding Modular Monolith**:
Modular monolith, also known as a monolith with bounded contexts or microservice-like architecture, aims to address some of the limitations of a traditional monolith while retaining its benefits. In this approach, the monolithic application is split into loosely-coupled modules, each representing a distinct domain or bounded context. These modules can be developed and deployed independently, leveraging the advantages of modular design while avoiding the complexities of a distributed system.

**Pros of Modular Monolith**:

- _Simplified Development_: modular monoliths provide a balance between the simplicity of traditional monoliths and the modularity of microservices. Developers can work on independent modules with well-defined boundaries, enabling faster development and testing cycles.
- _Easier Deployment and Scalability_: while a traditional monolith requires deploying the entire application even for small updates, modular monoliths allow for individual module deployment. This granular deployment approach simplifies versioning, reduces downtime, and offers better scalability options.
- _Improved Maintainability_: with modular monoliths, the separation of concerns makes it easier to maintain and update specific modules without affecting the entire system. Teams can focus on individual modules, reducing the risk of regression bugs and ensuring faster maintenance and bug fixes.
- _Shared Resources and Reduced Overhead_: Modules within a modular monolith can share resources such as databases, caching layers, and infrastructure, reducing duplication and operational overhead. This can lead to more efficient resource utilisation and cost savings.

**Cons of Modular Monolith:**

- _Limited Independence_: while modular monoliths promote independent development and deployment, they are still tightly coupled compared to true microservice architectures. Changes in one module may require coordination with other modules, which can limit the true potential of independent development.
- _Complexity with Scaling_: modular monoliths can face challenges when it comes to scaling specific modules. As the size of individual modules increases, it becomes harder to scale them independently, potentially requiring scaling of the entire monolith.
- _Increased Complexity in Testing_: testing a modular monolith requires considering inter-module dependencies, which can complicate the testing process. Ensuring proper integration testing and maintaining a comprehensive test suite across modules becomes crucial.
- _Challenges in Inter-module Communication_: communication between modules within a modular monolith introduces complexity. Developers need to establish well-defined communication patterns and enforce proper contracts to maintain loose coupling and minimize unintended dependencies.

Choosing between a modular monolith and a traditional monolith depends on the specific requirements of your project. While modular monoliths offer benefits such as simplified development, easier deployment, and improved maintainability, they also come with challenges in terms of limited independence and increased complexity in testing and communication. Consider the size and complexity of your application, scalability requirements, and development team’s expertise when making this architectural decision.

**How does a modular monolith work in practice?**

Let's break down the example of an online shop domain.

Consider the following project structure:
![Modular monolith project structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yo567n6ipz62qjtcm98o.png)

Basic rules for transferring information between domains:
- there must be a single point of communication with the domain, in our case it is <u>*LocalClient</u>
- all data that is transferred from the domain must be in a shared space, <u>see Shared namespaces</u>.

By following these rules you can isolate the logic of certain domains, leaving the place of communication only through the local client.
Here's a roughly example of what an <u>OrderService</u> might look like that communicates with other services exclusively through their local clients.

```php
namespace ModularMonolith\OrderDomain\Services;

use ModularMonolith\OrderDomain\Services\Exceptions\ProductNotAvailableException;
use ModularMonolith\OrderDomain\Shared\Models\OrderDto;
use ModularMonolith\ProductDomain\Shared\Models\ProductDto;
use ModularMonolith\UserDomain\Shared\UserLocalClient;
use ModularMonolith\WarehouseDomain\Shared\WarehouseLocalClient;

class OrderService {
    public function __construct(
        private readonly UserLocalClient        $userLocalClient,
        private readonly WarehouseLocalClient   $warehouseLocalClient,
    ) { }

    /**
     * @param ProductDto[] $products
     *
     * @throws ProductNotAvailableException
     */
    public function createOrderForUser(string $userUuid, array $products): OrderDto {
        $user = $this->userLocalClient->getUserByUuid($userUuid);

        foreach ($products as $product) {
            $productUuid = $product->getUuid();
            if (!$this->warehouseLocalClient->isProductAvailable($product->getUuid())) {
                throw new ProductNotAvailableException($productUuid);
            }

            $this->warehouseLocalClient->withdrawProduct($product->getUuid());
        }

        //to do something to store order to certain $user
        //$this->saveOrder($user, $products);
    }
}

```

Using a modular monolith is a great way to prepare your project for the future transition to microservices.

For a deeper understanding, I recommend reading the book {% embed https://www.amazon.co.uk/gp/product/0137355467 %}

Cheers.