The microservices vs. monolith debate has produced more heat than light. Conference talks champion microservices as the path to scalability. Blog posts from companies that migrated back to monoliths warn about distributed systems complexity. The reality is that this is not a technical religion. It is a business decision that depends on your team size, growth trajectory, and operational maturity. Choosing wrong in either direction costs real money and real time.
What Monolith Actually Means in 2024
A monolith is not a pejorative. It is a single deployable unit that contains all of your application’s functionality. Ruby on Rails, Django, Laravel, and Spring Boot all produce monoliths by default, and some of the most successful products in history, Shopify ($185 billion GMV), Basecamp, Stack Overflow, run on monoliths.
A well-structured monolith is not a “big ball of mud.” It has clearly defined modules, separated concerns, and internal boundaries. Shopify runs on a monolithic Rails application with hundreds of modules that have defined interfaces and ownership. They call it a “modular monolith,” and it supports a codebase with millions of lines of code and thousands of developers.
The advantages of a monolith are concrete and measurable:
Deployment simplicity. One application to build, test, and deploy. A single CI/CD pipeline. No service mesh, no distributed tracing (yet), no inter-service authentication. A deployment that takes 5 minutes instead of coordinating 15 service deployments.
Local development speed. One docker-compose up or rails server command and the entire application runs on a laptop. No need to manage service discovery, local Kubernetes clusters, or mock services for dependencies that are not running.
Transactional integrity. A database transaction in a monolith can span multiple operations atomically. Create the order, reserve inventory, and charge the payment in a single transaction. In a microservices architecture, this becomes a distributed saga with compensating transactions, eventual consistency, and significantly more failure modes.
Refactoring ease. Renaming a function, changing a data model, or moving logic between modules is a code change in a monolith. In microservices, it is a coordinated API migration across services with backward compatibility requirements and deployment ordering.
When Microservices Become Worth the Complexity
Microservices are not an upgrade from monoliths. They are a trade-off: you accept significant operational complexity in exchange for specific capabilities that a monolith cannot provide at your scale.
Independent scaling of components. If your image processing pipeline needs 50x the compute of your user authentication service, microservices let you scale each independently. In a monolith, you scale the entire application to meet the demands of its most resource-hungry feature, wasting money on over-provisioned capacity for everything else. A video processing platform might spend $8,000/month scaling a monolith to handle encoding load, but only $2,000/month if the encoding service scales independently while the API server runs on minimal infrastructure.
Independent deployment and team autonomy. When you have 50+ engineers working on the same product, a monolith creates merge conflicts, deployment bottlenecks, and coordination overhead. The payments team’s deployment should not be blocked by the search team’s failing test. Microservices let teams deploy independently on their own cadences. Amazon’s “two-pizza team” structure was designed around this principle: each team owns one or more services end-to-end.
Technology heterogeneity. A machine learning pipeline might perform best in Python with TensorFlow. A real-time messaging service might need Go’s concurrency model. A CRUD API might be most productive in TypeScript with Node.js. Microservices let each team choose the best tool for their specific problem. In a monolith, everyone uses the same language and framework regardless of fit.
Fault isolation. In a well-designed microservices system, a failure in the recommendation engine does not crash the checkout flow. Circuit breakers (implemented with libraries like resilience4j or Polly) prevent cascading failures by stopping calls to failing services and returning fallback responses. In a monolith, a memory leak in one module can crash the entire application.
The Decision Matrix: Honest Questions to Ask
Before choosing an architecture, answer these questions honestly:
How big is your engineering team? If you have fewer than 15 engineers, microservices will slow you down. The operational overhead of managing distributed systems, container orchestration, inter-service communication, and distributed debugging requires dedicated platform engineering capacity. A team of 5 engineers running 8 microservices spends more time on infrastructure than on product.
How well-defined are your domain boundaries? Microservices work when you can draw clear boundaries between business domains. If your system has strong domain boundaries (orders, inventory, shipping, payments), each maps naturally to a service. If your features are deeply intertwined and every request touches 6 services, you have a distributed monolith that is worse than a real monolith. Premature decomposition, splitting services before you understand the domain boundaries, is the number one cause of microservices failure.
What is your deployment frequency target? If you deploy once a week, a monolith is fine. If you need 50 deployments per day across 10 teams, microservices enable that. But the intermediate case, 2-5 deployments per day with a single team, is handled perfectly well by a monolith with good CI/CD.
Do you have operational maturity for distributed systems? Microservices require centralized logging (ELK, Datadog), distributed tracing (Jaeger, OpenTelemetry), service meshes or API gateways (Istio, Kong, AWS API Gateway), container orchestration (Kubernetes, ECS), and on-call rotations that cover each service. If you do not have this infrastructure or the team to manage it, microservices will create more outages than they prevent.
What is your budget for infrastructure and tooling? A monolith on a single $200/month server can handle remarkable traffic. The same application split into 8 microservices might require $1,500/month in Kubernetes cluster costs, plus $500/month in monitoring and logging infrastructure, plus the engineering time to manage it. The complexity tax is real and recurring.
See also: Privacy-First Software Development as Competitive Advantage
The Modular Monolith: The Best of Both Worlds
For most companies in the 5-50 engineer range, the modular monolith is the pragmatic choice. It provides the organizational benefits of microservices, clear module boundaries, defined interfaces, team ownership, without the operational complexity.
A modular monolith enforces boundaries at the code level rather than the network level. Each module:
- Has a defined public API (a set of service objects or functions that other modules can call).
- Owns its database tables (other modules access this data only through the module’s API, never by querying tables directly).
- Has its own test suite that runs independently.
- Is owned by a specific team.
In Rails, you can implement this with engines or packwerk. In Java, use multi-module Gradle or Maven projects with enforced dependency rules. In TypeScript, use a monorepo with Nx or Turborepo and strict module boundaries. Shopify’s packwerk tool specifically enforces module boundaries in Ruby applications, preventing accidental coupling.
The modular monolith gives you a migration path. When (and if) a module genuinely needs to be extracted into a separate service, the boundary is already clean. The extraction becomes a deployment change, not an architectural rewrite. You move the module to its own repository, replace internal function calls with API calls, and deploy it independently. This takes weeks, not months, because the hard work, defining the boundary, was already done.
Migration Patterns: Monolith to Services and Back
If you do decide to extract services from a monolith, the strangler fig pattern is the lowest-risk approach. Named after the tropical fig that grows around a host tree, this pattern gradually routes functionality from the monolith to new services without a big-bang rewrite.
Step 1: Identify the extraction candidate. Choose the module with the clearest boundary, the most independent data model, and the most to gain from independent scaling or deployment. Common first extractions: email/notification service, file processing, search indexing, or authentication.
Step 2: Build the new service alongside the monolith. The new service implements the same functionality and runs in parallel. Both the monolith and the new service share the same database initially (or the new service maintains a synchronized copy).
Step 3: Route traffic incrementally. Use a feature flag or API gateway to route a percentage of traffic to the new service. Start at 1%, monitor error rates and latency, and increase gradually.
Step 4: Cut over and decommission. Once 100% of traffic flows through the new service with stable metrics, remove the old code from the monolith and complete the database separation.
This pattern typically takes 2-4 months per service extraction. It is slower than a rewrite but carries a fraction of the risk. Each step is reversible. At no point is the system in a state where a failure requires rolling back weeks of work.
The reverse migration, consolidating microservices back into a monolith, follows similar incremental steps. Amazon Prime Video publicly documented moving a monitoring service from microservices back to a monolith, reducing costs by 90% and simplifying operations. There is no shame in this. It means the team evaluated the trade-offs and made the pragmatic choice.
The best architecture is the one that lets your current team ship reliably at your current scale, with a clear path to evolve as your needs change. Start with the simplest thing that works, and add complexity only when the concrete benefits outweigh the concrete costs.
Not sure which architecture fits your product and team? Let us help you evaluate the trade-offs. We have built systems at both ends of the spectrum and know where the pitfalls are.