14 min left
Back to Series

The Stack > Article 59 | Intermediate | 14 min read

Article 59Intermediate14 min read

When should you actually break up your monolith? Sam Newman on microservices and resilience

Sam Newman on when microservices make sense, why most distributed systems fail the same ways, and how to define service boundaries that last.


When should you actually break up your monolith? Sam Newman on microservices and resilience

Most teams break up their monolith too early, for the wrong reasons, and pay for it in operational complexity they didn't budget for. The question isn't whether microservices are good or bad—it's whether your organization can handle the tradeoffs that come with distributed systems. Sam Newman, author of Building Microservices and Monolith to Microservices, has spent years helping teams navigate this decision, and his advice boils down to one principle: solve the problem you actually have, not the one you think you'll have in three years.

This article walks through the organizational and technical conditions that make microservices worth the cost, the failure modes that trip up most distributed systems, how to carve service boundaries that won't need rework, and what AI tooling is changing about these decisions today.

Organizational and technical preconditions for migration

Microservices solve organizational problems more than technical ones. If your team can't deploy independently, collaborate across boundaries, or own services end-to-end, splitting up your codebase will make those problems worse. The architecture follows the org chart, not the other way around.

Conway's Law states that systems mirror the communication structure of the organization that builds them. Think of it like office layout: a single open floor plan enables quick hallway conversations and shared context. Separate buildings with locked doors force formal communication channels. A monolith with a single team shipping on a shared schedule can move faster than a distributed system with five teams that need cross-service coordination for every feature. Breaking up the monolith before you can break up the team just adds network calls to your deployment process.

The real question isn't "Should we use microservices?" It's "Can our organization actually operate a distributed system?" Most can't, and that's fine.

Newman points to preconditions that need to exist before a migration makes sense. You need independent deployability—the ability to ship one service without coordinating releases across the system. You need team autonomy—clear ownership of services with minimal handoffs. You need production readiness—monitoring, logging, tracing, and incident response that works across service boundaries. If any of these are missing, the migration will stall out in a half-migrated mess that's harder to operate than either a clean monolith or a clean distributed system.

Technical preconditions matter too. Your deployment pipeline should already support multiple artifacts. Your data model should have natural seams where bounded contexts emerge. Your team should understand asynchronous communication patterns like message queues and event streams, because synchronous HTTP (Hypertext Transfer Protocol) calls between services create cascading failures that are hard to debug. If you're still figuring out how to deploy a single service reliably, adding more services won't help.

Common failure modes in distributed systems

Distributed systems fail in predictable ways, and most teams hit the same problems in the same order. Network partitions. Timeouts. Retry storms. Cascading failures. The shift from monolith to microservices turns function calls into network requests, and every network request is an opportunity for things to go wrong.

Partial failure is the core problem. In a monolith, when a function fails, the entire process knows about it immediately. In a distributed system, when a service fails, other services keep running—and keep trying to talk to the failed service. Without backpressure or circuit breakers, one slow service can take down the entire system as request queues fill up and timeouts cascade.

Think of a circuit breaker like a household breaker panel. When too much current flows through a wire, the breaker trips to prevent a fire. In software, when too many requests to a service fail, the circuit breaker stops sending requests for a cooldown period, giving the downstream service time to recover. Libraries like Hystrix (now in maintenance mode) and Resilience4j implement this pattern, but the hard part isn't the code—it's deciding where to place the breakers and what to do when they trip.

Failure modeMonolith behaviorMicroservices behaviorMitigation
Slow dependencyThread blocks, request times outService times out, retries amplify loadCircuit breaker, timeout tuning
Database lockTransaction waits or deadlocksMultiple services contend, deadlock spans networkSaga pattern, optimistic locking
Memory leakProcess crashes, restartsOne service crashes, others keep calling itHealth checks, graceful degradation
Deploy errorRollback entire appRollback one service, version skew with othersAPI versioning, contract testing

Observability becomes non-negotiable in a distributed system. You can't grep logs across twenty services. You need structured logging with correlation IDs that track requests across service boundaries, distributed tracing that shows where time is spent, and metrics that aggregate across the fleet. Tools like OpenTelemetry provide a standard way to instrument code, but making sense of the data requires discipline—tagging requests with business context, sampling traces intelligently, and building dashboards that surface problems before users notice.

Retry logic is another failure vector. A service times out, the client retries, the service is still overloaded, more retries pile up, and suddenly you have a retry storm where 90% of requests are retries. The fix is exponential backoff with jitter—wait a little longer after each failure, and add randomness so retries don't synchronize. Libraries handle this, but you have to configure them correctly, and most teams learn this the hard way during their first incident.

Service boundary definition as the critical architectural decision

Drawing service boundaries wrong is the most expensive mistake you can make in a microservices migration. Get it wrong and you'll spend years refactoring integrations, coordinating deploys, and migrating data between services that shouldn't have been split in the first place.

Bounded contexts from domain-driven design (DDD) provide the best starting point. A bounded context is a boundary within which a particular model is valid. An "order" in the checkout context means something different than an "order" in the fulfillment context, even though they share a name. Each context has its own language, its own rules, and its own data. When you split services along bounded context lines, you get independent models that can evolve separately.

Think of it like dividing a city into neighborhoods. Each neighborhood has its own character, its own services (grocery store, school, park), and its own governance. Residents mostly stay in their neighborhood, crossing boundaries only when necessary. Services work the same way—most interactions happen within a bounded context, and cross-context calls are explicit integrations with well-defined contracts.

The mistake teams make is splitting on technical layers instead of domain boundaries. One service for the database, one for business logic, one for the API (Application Programming Interface). This creates a distributed monolith—you have all the complexity of microservices with none of the benefits, because every feature still requires coordinating changes across all three services. A better split is vertical: the checkout service owns its data, logic, and API surface. The fulfillment service does the same. They communicate through events or synchronous APIs, but each can evolve independently.

Data ownership is the forcing function. If two services share a database, they're not independent—they're a monolith with extra steps. Each service should own its data completely, and other services should access that data only through the service's API. This means duplicating some data across services, accepting eventual consistency, and designing integrations that can handle stale reads. It's harder than sharing a database, but it's the only way to get true independence.

Newman warns against the "anemic microservice" pattern—services that are just CRUD (Create, Read, Update, Delete) operations over a database with no business logic. These services don't align with bounded contexts, they create chatty networks with high latency, and they spread domain logic across the system where it's hard to reason about. A good service is cohesive—it contains the data and logic for one well-defined domain concept, and it presents a rich API that hides its implementation details.

Operational complexity tradeoffs

Microservices shift complexity from the codebase to the runtime environment. A monolith has complex code but simple operations—one process to deploy, one log file to read, one thing to restart when something breaks. Microservices have simpler code (in theory) but complex operations—dozens of processes, distributed logs, network failures, version skew, and cross-service debugging.

Deployment complexity scales with the number of services. Each service needs its own continuous integration/continuous deployment (CI/CD) pipeline, its own deployment schedule, its own rollback strategy. Coordinating a feature that spans three services means orchestrating three deploys, handling version compatibility, and managing the state where the system is half-upgraded. Tools like Kubernetes help, but they introduce their own complexity—YAML (Yet Another Markup Language) configuration, pod scheduling, service meshes, and a learning curve that takes months to climb.

Monitoring and debugging get harder. In a monolith, a stack trace tells you exactly where an error happened. In a distributed system, a request might touch five services before failing, and the stack trace only shows you the last hop. You need distributed tracing to reconstruct the request path, correlation IDs to link logs across services, and an observability platform that can query terabytes of telemetry data in real time. The tooling exists—Jaeger, Zipkin, Honeycomb, Datadog—but running it costs money and requires expertise.

ConcernMonolithMicroservicesNotes
Deploy frequencyWeekly/monthly releasesMultiple deploys per day per serviceRequires mature CI/CD
Debug timeSingle stack traceMulti-service trace reconstructionObservability tax is real
Data consistencyACID transactionsEventual consistency, sagasHarder to reason about
Ops team size1-2 engineers can manageNeed platform/SRE teamOr managed services
Incident responseSingle process to restartIdentify failing service, check dependenciesRequires runbooks

A microservices architecture is a bet that the agility you gain from independent deployability is worth the operational complexity you're taking on. For many teams, it's a bad bet.

The staffing requirement changes too. A monolith can be operated by a small team—two engineers can handle deploys, monitoring, and incident response. Microservices need a platform team that maintains the infrastructure, a site reliability engineering (SRE) function that handles production issues, and enough engineers on each service team to be on-call without burning out. If you have ten engineers total, you probably can't afford this overhead. If you have fifty, you might be able to. If you have two hundred, you need it.

Newman's advice is to stay monolithic longer than you think, and only split when the pain of coordinating work in the monolith exceeds the pain of operating distributed systems. That threshold is different for every team, but it's usually later than founders expect.

How AI tooling is changing architectural decision-making

Large language models (LLMs) and AI-assisted coding tools are changing how teams make architectural decisions, but not in the ways you'd expect. The promise is that AI will auto-generate microservices from requirements or refactor monoliths automatically. The reality is that AI is better at explaining tradeoffs and generating boilerplate than at making high-stakes architectural calls.

Tools like GitHub Copilot and ChatGPT excel at generating service scaffolding—controller boilerplate, API client code, test fixtures, deployment configs. This lowers the activation energy for creating a new service, which can be good (less time on boilerplate) or bad (teams spin up more services than they should because it's easy). The barrier to creating a new service used to be the effort of setting up the repo, CI/CD, and infrastructure. Now it's a few prompts. This makes it easier to create services, but it doesn't help you decide whether you should.

AI is more useful for exploring architectural options. You can describe a problem to an LLM—"I have a checkout flow that needs to coordinate inventory, payments, and shipping"—and get back several service decomposition strategies with tradeoffs. The output isn't production-ready, but it's a starting point for discussions. It surfaces patterns (saga, event sourcing, CQRS) that the team might not know about, and it explains them in context. This works better for intermediate engineers who understand enough to evaluate the suggestions than for beginners who take the output at face value.

Contract testing and API design are areas where AI helps. Tools can generate OpenAPI specs from code, suggest API changes that maintain backward compatibility, and write contract tests that verify service integrations without running the full stack. Pact and similar frameworks automate this, and LLMs can help write the test cases and interpret failures. This reduces the risk of breaking integrations when you change a service's API, which is one of the main failure modes in microservices.

The bigger shift is in documentation and knowledge transfer. Microservices require more written context—why a service exists, what bounded context it represents, how to deploy it, what failure modes to watch for. AI tools can draft runbooks, generate architecture decision records (ADRs), and keep READMEs up to date by inferring structure from code and commit history. This doesn't replace human judgment, but it makes it cheaper to maintain the documentation debt that comes with distributed systems.

Newman sees AI as a tool for lowering the cognitive load of operating microservices, not a replacement for architectural thinking. The questions—where to draw boundaries, when to split, how to handle consistency—still require human judgment grounded in the business domain. AI can accelerate the grunt work (writing YAML, generating tests, drafting docs), but it can't tell you whether your organization can actually operate a distributed system. That's still on you.

What this means for builders

If you're deciding whether to break up a monolith, start with the organization, not the code. Can your teams deploy independently? Do you have enough engineers to staff on-call rotations for multiple services? Can you afford the observability stack and platform engineering effort that microservices require? If the answer to any of these is no, stay monolithic and invest in making the monolith easier to work with—better module boundaries, clearer code ownership, faster test suites.

If you do migrate, split on domain boundaries, not technical layers. Use bounded contexts from domain-driven design to find natural seams in your model. Prioritize independent data ownership—each service gets its own database, and cross-service access happens through APIs or events. Invest in observability before you need it, because debugging distributed systems without tracing and structured logs is nearly impossible.

Design for failure from the start. Add circuit breakers, implement retries with exponential backoff and jitter, handle partial failures gracefully. Make sure your services can degrade functionality when dependencies are down rather than cascading failures across the system. Write runbooks for common failure modes, and practice incident response in non-production environments.

Use AI tools to lower the friction of creating and maintaining services, but don't let them drive architectural decisions. Let them generate boilerplate, draft documentation, and suggest patterns, but evaluate every suggestion against your actual organizational constraints and domain model. The cost of a wrong service boundary is years of refactoring, and no LLM can see that coming.

Above all, treat the decision to adopt microservices as reversible in theory but expensive in practice. You can always add services later. Removing them or merging them back into a monolith is much harder. Start small, prove the operational model works, then expand. If you're a startup, you almost certainly don't need microservices yet. If you're a scale-up, you might, but only if you're hitting real pain in deploy coordination and team autonomy. If you're at enterprise scale, you probably already have them, and the work is improving boundaries and operational maturity, not adding more services.

Conclusion

The microservices question isn't about technology—it's about whether your organization can handle the operational complexity that comes with distributed systems. Most teams underestimate the cost and overestimate the benefits. Monoliths are underrated, and staying monolithic longer than conventional wisdom suggests often leads to better outcomes. When you do split, do it for organizational reasons (independent teams, faster deploys) backed by technical readiness (observability, deployment automation, failure handling). Draw boundaries along domain lines, not technical layers. Design for failure. And use AI tools to reduce toil, not to make architectural decisions. The payoff for getting this right is agility and scale. The cost of getting it wrong is years of technical debt and operational fire drills.


the-stackintermediatemicroservicesdistributed-systemsarchitecturesystem-designresilience

Up next in the series

Article 60Live

When AI agents go rogue on government systems: lessons from Anthropic's visa application incident

Anthropic's accidental visa application revealed how AI agents can trigger real actions on government systems—and what builders must do differently.

the-stackintermediateai-agentsautonomous-systems