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.
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.
2. The Modular Monolith: The Architectural “Sweet Spot”

The answer for developing software like BOMA lies in the Modular Monolith. But what does that mean exactly?
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.
3. Domain-Driven Design (DDD) and Bounded Contexts

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:
- Core/CRM Module: Management of Records, Leads, Opportunities.
- Billing Module: Management of quotes, invoices, recurring payments.
- 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);
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.customersbilling_module.invoicessupport_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:
- Request the data via the internal API of the CRM module.
- Or, maintain a redundant local copy of the necessary data (e.g., Company Name, VAT ID) updated via in-process Domain Events.
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

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.
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.
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.
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.
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.
Still have doubts about CRM Development: Monolith vs Microservices and the Choice of the Modular Monolith?
Type your specific question here to instantly find the official reply from Google.
Sources and Further Reading






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.