The microservices vs monolith debate has evolved considerably. After a decade of microservices enthusiasm — where many teams adopted distributed architectures prematurely and paid the price in complexity — the industry has developed a more nuanced understanding of when each approach makes sense. The answer, as experienced engineers have always suspected, is "it depends."
At StrikingWeb, we have built both monolithic and microservices architectures for clients ranging from early-stage startups to large enterprises. This article shares our framework for making the architecture decision in 2025, including the increasingly popular hybrid approaches that blend the best of both worlds.
The Real Cost of Microservices
Microservices offer genuine benefits — independent deployment, technology flexibility, team autonomy, and targeted scaling. But these benefits come with significant costs that are often underestimated.
Operational Complexity
Running a microservices architecture requires sophisticated infrastructure:
- Service discovery and routing — Services need to find and communicate with each other reliably
- Distributed tracing — Debugging a request that spans ten services requires tooling like Jaeger or Datadog APM
- Configuration management — Each service has its own configuration, secrets, and environment-specific settings
- CI/CD pipelines — Each service needs its own build, test, and deployment pipeline
- Monitoring and alerting — Aggregating logs, metrics, and traces across dozens or hundreds of services
- Network reliability — Handling timeouts, retries, circuit breakers, and partial failures
Data Management Challenges
Microservices complicate data management significantly. Each service owns its own data, which means you lose the ability to perform simple JOIN queries across domain boundaries. Maintaining data consistency across services requires patterns like Saga, event sourcing, or eventual consistency — all of which add complexity and introduce failure modes that do not exist in a monolith.
Team and Organization Requirements
Conway's Law applies strongly here: your architecture will mirror your organization's communication structure. Microservices work well when you have multiple autonomous teams that need to deploy independently. They are counterproductive when a small team is maintaining all the services, because the team is paying the distributed systems tax without getting the organizational benefit.
"We have seen startups with five engineers running fifteen microservices. They spent more time on infrastructure and inter-service communication than on building features. A well-structured monolith would have served them far better."
The Monolith Renaissance
The industry is rediscovering the virtues of monolithic architecture — not the tangled, unstructured monoliths of the past, but well-designed modular monoliths that provide many of the organizational benefits of microservices without the distributed systems complexity.
Advantages of a Well-Built Monolith
- Simplicity — One codebase, one deployment, one database. Debugging is straightforward because everything runs in the same process.
- Development speed — No need to set up inter-service communication, manage distributed transactions, or maintain dozens of CI/CD pipelines.
- Refactoring ease — Changing a shared data model or refactoring a domain boundary is a code change, not a cross-service coordination exercise.
- Performance — In-process function calls are orders of magnitude faster than network calls between services.
- Testing simplicity — Integration testing is straightforward when all components run in the same process.
The Modular Monolith — The Best of Both Worlds
The modular monolith has emerged as a compelling middle ground. It is a monolithic deployment — a single application running in a single process — but internally organized into well-defined modules with clear boundaries, explicit interfaces, and enforced encapsulation.
Key Principles
# Modular Monolith Structure
/src
/modules
/users # User management module
/api # Public interface (what other modules can call)
/internal # Implementation details (hidden from other modules)
/data # Module's database schema and repositories
/orders # Order management module
/api
/internal
/data
/inventory # Inventory module
/api
/internal
/data
/shared # Truly shared utilities and infrastructure
The critical rule is that modules communicate only through their public APIs — never by reaching into another module's internal data or implementation. This creates the same logical boundaries that microservices enforce through network separation, but without the operational overhead.
Enforcing Module Boundaries
Without enforcement, module boundaries erode over time. We recommend using architectural fitness functions and static analysis tools to enforce boundaries. Tools like ArchUnit (Java), NetArchTest (.NET), and dependency-cruiser (JavaScript/TypeScript) can automatically verify that module dependencies follow the defined rules.
Decision Framework — When to Use What
Start with a Monolith When
- You are building a new product and the domain boundaries are not yet clear
- Your team is smaller than 15-20 engineers
- You need to move fast and iterate rapidly on the product
- Your operational maturity does not yet include containerization, service mesh, and distributed tracing
- The application does not have extreme scaling requirements for individual components
Consider Microservices When
- You have multiple teams (3+) that need to deploy independently
- Different components have vastly different scaling requirements
- Different components benefit from different technology stacks
- You have the operational maturity to manage distributed systems
- Domain boundaries are well-understood and stable
- Your organization's structure naturally maps to service boundaries
The Hybrid Approach
Many successful architectures are hybrids. A modular monolith handles the core application logic, while specific capabilities are extracted into separate services when there is a clear reason — a component that needs to scale independently, a capability that benefits from a different technology stack, or a team that needs deployment autonomy.
Common candidates for early extraction include:
- Real-time features — WebSocket servers, notification services, and event streaming often benefit from separate deployment
- Media processing — Image and video processing is CPU-intensive and benefits from independent scaling
- Search — Full-text search often uses specialized technology (Elasticsearch, Meilisearch) that runs separately
- Authentication — Identity and access management is a well-defined domain with clear boundaries
Migration Strategies
If you are considering migrating from a monolith to microservices (or the reverse), proceed incrementally. The Strangler Fig pattern — gradually replacing monolith functionality with services while keeping the monolith running — is the safest approach. Extract one module at a time, starting with the module that has the clearest boundary and the most independent scaling needs.
Going in the other direction — consolidating microservices back into a monolith or modular monolith — is also a valid and increasingly common decision. If your microservices architecture is causing more problems than it solves, there is no shame in simplifying.
Our Recommendation
For most projects we work on at StrikingWeb, we recommend starting with a modular monolith and extracting services only when there is a clear, measurable reason to do so. This approach gives you the organizational benefits of clear module boundaries, the simplicity of monolithic deployment, and a clear path to microservices if and when you need them.
The best architecture is the one that lets your team deliver value to users quickly and reliably. If you are making an architecture decision and want an experienced perspective, we are here to help.