CRM Development: Monolith vs Microservices and the Choice of the Modular Monolith

Published on Jan 12, 2026
Updated on Jan 12, 2026
reading time

This article is also available in:French, German, Spanish, Portuguese, Romanian, Italian
Software architecture diagram: comparison between microservices and modular monolithAI-generated image

AI-generated images Details

It is 2026, and the most heated architectural debate of the last decade seems to have reached a phase of pragmatic maturity. When discussing monolith vs microservices, the industry has finally stopped chasing blind hype to focus on ROI (Return on Investment) and operational efficiency. For an SME or a software house developing B2B CRM solutions (like the BOMA case study), the choice of architecture is not just technical, but strategic.

In this in-depth technical guide, we will analyze why the pure microservices approach is often a trap for agile teams and how the Modular Monolith represents the definitive solution to balance development speed, maintainability, and scalability.

Advertisement

1. Monolith vs Microservices: The Operational Reality in 2026

For years, the dominant narrative was: “The monolith is the past, microservices are the future.” However, field experience has proven that for most business applications, especially in the context of CRMs for SMEs, microservices introduce unsustainable accidental complexity.

The Hidden Cost of Microservices

Adopting a microservices architecture requires enormous infrastructure overhead. It is not just about writing code, but managing:

  • Network Latency: In-process calls become RPC/HTTP calls, introducing failures and latencies.
  • Eventual Consistency: Managing distributed transactions (Saga Pattern) is exponentially more complex than the ACID transactions of a relational database.
  • Observability: The need for complex tools for distributed tracing (e.g., OpenTelemetry) to understand where a request fails.

As highlighted by Martin Fowler back in the early 2020s, the golden rule remains: “Don’t use microservices unless you have a specific reason to do so”. For a CRM that needs to manage records, invoices, and tickets, the physical separation of services often creates a Distributed Monolith: the worst of both worlds, where you have the rigidity of the monolith and the complexity of distributed systems.

Discover more →

2. The Modular Monolith: The Architectural “Sweet Spot”

CRM Development: Monolith vs Microservices and the Choice of the Modular Monolith - Summary Infographic
Summary infographic of the article “CRM Development: Monolith vs Microservices and the Choice of the Modular Monolith” (Visual Hub)

The answer for developing software like BOMA lies in the Modular Monolith. But what does that mean exactly?

Advertisement

A Modular Monolith is a single deployment unit (a single artifact, e.g., a .jar file or a single Docker image) where the code is internally structured into highly cohesive and loosely coupled modules. Unlike the “Spaghetti Monolith” (Big Ball of Mud), dependencies here are strictly controlled.

Advantages for an Agile Team

  • Simplified Refactoring: Moving code between modules is an IDE operation, not a system migration.
  • Atomic Deployment: A single CI/CD pipeline releases the entire application, eliminating versioning issues between services.
  • Performance: Zero-latency in-memory communication between modules.
Discover more →

3. Domain-Driven Design (DDD) and Bounded Contexts

Schematic comparison between microservices software architecture and modular monolithAI-generated image
The modular monolith outperforms microservices by ensuring efficiency and scalability in CRM software development.

To build an effective Modular Monolith, applying Domain-Driven Design (DDD) principles is mandatory. We must identify the Bounded Contexts that make up our CRM.

Let’s imagine BOMA’s architecture divided into three main modules:

  1. Core/CRM Module: Management of Records, Leads, Opportunities.
  2. Billing Module: Management of quotes, invoices, recurring payments.
  3. Ticketing Module: Customer support, SLAs, issue resolution.

Definition of Public Interfaces

The fundamental rule is that no module can directly access the internal classes or database tables of another module. Communication occurs only through public interfaces (internal APIs).


// Violation example (BAD PRACTICE)
var fatture = _invoiceRepository.GetByCustomerId(customer.Id);

// Correct example in Modular Monolith (GOOD PRACTICE)
// The CRM module invokes a public interface of the Billing module
var fatture = _invoiceModuleApi.GetInvoicesForCustomer(customer.Id);
Discover more →

4. Data Management: Logical vs. Physical Separation

One of the most common mistakes in the monolith vs microservices debate concerns the database. In the Modular Monolith, while having a single physical instance of the database (to save costs and facilitate backups), we must enforce strict logical separation.

Schema Strategy (Schema-per-Module)

If we use PostgreSQL or SQL Server, each module must possess its own database schema:

  • crm_module.customers
  • billing_module.invoices
  • support_module.tickets

It is forbidden to perform JOINs between tables of different schemas. If the Billing module needs Customer data to print an invoice, it must:

  1. Request the data via the internal API of the CRM module.
  2. Or, maintain a redundant local copy of the necessary data (e.g., Company Name, VAT ID) updated via in-process Domain Events.
Read also →

5. Code Refactoring: From Layers to Features

Folder organization mirrors the architecture. We abandon the classic division by technical layers (Controllers, Services, Repositories) in favor of a division by Modules/Features.

Recommended Directory Structure


/src
  /Modules
    /Crm
      /Api (Public contracts exposed to other modules)
      /Core (Internal implementation: Domain, Infra, App Services)
    /Billing
      /Api
      /Core
    /Ticketing
      /Api
      /Core
  /Shared (Shared Kernel, event bus, cross-cutting utilities)
  /Host (Entry point, Startup, DI Configuration)

This approach allows different teams to work on different modules without stepping on each other’s toes, simulating the independence of microservices but maintaining the simplicity of the monolith.

6. CI/CD Deployment Strategies

The Modular Monolith excels in iteration speed. Here is how to configure a modern CI/CD pipeline for this scenario:

  • Single Build: Compilation verifies the integrity of all modules simultaneously. If a change in the CRM module breaks the Billing module, the build fails immediately (rapid feedback loop).
  • Automated Tests:
    • Unit Tests: Isolated within each module.
    • Integration Tests: Verify module operation with the database (in ephemeral containers).
    • Architecture Tests: Use libraries like ArchUnit or NetArchTest to prevent via code that module A uses internal classes of module B.
  • Blue/Green Deployment: Being a single artifact, it is easy to spin up the new version alongside the old one, switch traffic, and rollback immediately in case of errors.

Conclusions: When to Migrate?

The transition from Modular Monolith to Microservices should only happen when a single module becomes so complex or requires such different resources (e.g., an AI module requiring GPUs) that its extraction into an autonomous service is justified.

Until that moment, for the development of CRMs and management software in SMEs, the Modular Monolith remains the most efficient, economical, and robust architecture. It allows writing clean code today, leaving the door open to physical distribution tomorrow, without paying the price of complexity in advance.

Frequently Asked Questions

disegno di un ragazzo seduto con nuvolette di testo con dentro la parola FAQ
What is the Modular Monolith and what advantages does it offer?

The Modular Monolith is a software architecture composed of a single deployment unit where the code is organized into cohesive and independent modules. This solution represents the ideal balance point for SMEs as it guarantees the speed of in-memory communication and the management simplicity typical of the classic monolith, while offering the code cleanliness and structural maintainability usually sought in microservices.

Why can microservices be risky for an SME?

Adopting microservices without a real need introduces accidental complexity that is often unsustainable for agile teams, known as infrastructure overhead. Companies find themselves having to manage network latency, eventual data consistency, and distributed transactions, risking the creation of a rigid and hard-to-observe system that combines the defects of the old monolith with the difficulties of distributed systems.

How should the database be managed in the Modular Monolith?

Even if a single physical database instance is used to optimize costs, strict logical separation must be applied using dedicated schemas for each module. JOIN operations between tables of different schemas are forbidden, and data exchange must occur exclusively via public internal APIs or domain events, thus ensuring that each module remains decoupled from the others.

When is it advisable to migrate from Monolith to Microservices?

The transition to microservices should only occur when a specific module reaches such complexity that it justifies its extraction or requires different hardware resources, such as GPUs for artificial intelligence calculations. Until that moment, maintaining a unified modular architecture allows for fast development and simplified software release without unnecessary complications.

How does Domain Driven Design help CRM development?

Domain Driven Design is fundamental for defining Bounded Contexts, which are the logical boundaries separating CRM functional areas such as sales, billing, and support. Applying these principles ensures that dependencies are controlled and that no module directly accesses the internal logic of another, facilitating code maintenance and allowing different teams to work in parallel without conflicts.

Francesco Zinghinì

Engineer and digital entrepreneur, founder of the TuttoSemplice project. His vision is to break down barriers between users and complex information, making topics like finance, technology, and economic news finally understandable and useful for everyday life.

Did you find this article helpful? Is there another topic you’d like to see me cover?
Write it in the comments below! I take inspiration directly from your suggestions.

AI-generated questions and answers

The questions and comments below are generated by an artificial intelligence system and the answers come from Simply, the TuttoSemplice.com virtual assistant. They do not come from real users.

AI-generated comment

AI-generated question

Great article. You mentioned using libraries like ArchUnit to enforce boundaries. I’m working in a .NET environment. Is there an equivalent tool you recommend to stop my juniors from referencing the internal Core of another module?

Simply · AI virtual assistant

Hello! Yes, for .NET the standard standard is **NetArchTest**. It allows you to write fluent unit tests like:

`Types.InAssembly(CrmAssembly).ShouldNot().HaveDependencyOn(BillingAssembly)`

It’s fantastic for CI/CD pipelines because it fails the build if someone breaks the architectural rules. Highly recommended.

AI-generated question

I’m a bit skeptical about the folder structure part. Moving from technical layers (Controllers/Services) to Feature slices feels chaotic in Visual Studio. Do you put the DTOs inside the Feature folder too? And where do you put generic utilities like string formatters?

Simply · AI virtual assistant

Hi, valid concern. It requires a mindset shift. To answer your specific points:

1. **DTOs:** Yes, DTOs specific to a use case go inside that Module/Feature folder. This keeps the code highly cohesive—everything related to ‘Creating an Invoice’ is in one place.
2. **Utilities:** For generic code used across the whole system (like string formatters, date extensions, or base classes), we use a `Shared` or `Common` kernel module that other modules can reference. Just be careful not to put business logic there!

AI-generated question

Quick question on the transactions part. Since it is a single physical database, can we still use ACID transactions across modules? For example, if creating a Ticket fails, I want to rollback the Customer update too.

Simply · AI virtual assistant

Hi. Technically, yes—since you share the same physical DB connection, you *can* wrap cross-module operations in a single transaction scope.

However, **I advise against it** as a default practice. If you rely on cross-module ACID transactions, you are coupling them tightly. If you ever decide to split them into microservices later, you’ll have a hard time. Ideally, use *Domain Events* for eventual consistency between modules, but keep strong consistency (ACID) only within the boundaries of a single module/aggregate.

AI-generated question

This is exactly what our team needed to hear. We spent the last 18 months trying to break our legacy CRM into microservices and created a ‘distributed monolith’ nightmare just like you described. The network latency issues were real. One question though: regarding the ‘Schema-per-Module’ strategy, how do you handle complex reporting? If I can’t do JOINs between the Billing and CRM tables, doesn’t that make generating a monthly sales report incredibly slow?

Icona WhatsApp

Subscribe to our WhatsApp channel!

Get real-time updates on Guides, Reports and Offers

Click here to subscribe

Icona Telegram

Subscribe to our Telegram channel!

Get real-time updates on Guides, Reports and Offers

Click here to subscribe

Advertisement
Simply - Virtual Assistant
Hi! I am Simply, TuttoSemplice virtual assistant. How can I help you today?
Condividi articolo
1,0x
Table of Contents