Loading learning content...
How big should a microservice be? This seemingly simple question has launched a thousand debates and derailed countless architecture reviews.
The term "microservice" itself is misleading—it suggests services should be micro, leading teams to decompose aggressively. But services can also be too small, creating an explosion of tightly-coupled nano-services that require complex orchestration for simple operations.
The truth is that there is no universal right size. Service granularity is a tradeoff with costs on both extremes. Too coarse-grained and you recreate the monolith's problems. Too fine-grained and you drown in distributed systems complexity without realizing microservices' benefits.
This page develops principles for finding the right granularity—the Goldilocks zone where services are "just right" for your specific context.
By the end of this page, you will understand the tradeoffs at both extremes of granularity, develop heuristics for right-sizing services, learn to recognize when services are too big or too small, and apply practical criteria for granularity decisions.
Service granularity exists on a spectrum from very coarse (few large services) to very fine (many small services). Both extremes have costs and benefits:
Coarse-Grained Services (Too Big)
At this extreme, you have a small number of large services, each handling multiple bounded contexts or business capabilities.
Benefits:
Costs:
At the extreme, you have... a monolith.
Fine-Grained Services (Too Small)
At this extreme, you have many small services, sometimes one per entity or operation.
Benefits:
Costs:
| Concern | Too Coarse | Just Right | Too Fine |
|---|---|---|---|
| Deployment | Large, coordinated releases | Independent, frequent deploys | Tiny, but constantly needed |
| Latency | Low (local calls) | Acceptable (few hops) | High (many hops) |
| Consistency | Easy (local transactions) | Manageable (events) | Hard (distributed sagas) |
| Team Autonomy | Low (conflicts) | High (clear ownership) | Very High (isolation) |
| Operational Cost | Low (few services) | Moderate | High (many services) |
| Change Impact | High (broad risk) | Contained | Very Low (but many changes) |
A startup with three engineers should have coarser-grained services than a 500-person engineering org. Your team's operational maturity, domain complexity, and business requirements all influence the right granularity. There's no universal number of services that's 'correct.'
Services that are too large create problems that may not be immediately obvious. Watch for these warning signs:
1. Release Congestion
When multiple teams or features compete to get changes into the same service, releases become bottlenecked. Sprint planners devolve into negotiating "who goes first." Features queue up waiting for their turn.
Evidence: Long periods between production deployments, merge conflicts as a regular occurrence, "release train" scheduling meetings.
2. Blast Radius Too Wide
When a defect in one area of the service takes down unrelated functionality. A bug in checkout logic shouldn't affect product browsing, but in an overloaded service, it might.
Evidence: Outages that affect unrelated features, rollbacks that lose unrelated changes, production incidents traced to code far from the symptoms.
3. Testing Becomes Slow and Flaky
As services grow, test suites grow. Integration tests become brittle because they exercise too many pathways. Developers stop running full test suites locally because they take too long.
Evidence: CI pipelines exceeding 30+ minutes, flaky tests that "usually pass," developers committing without running tests.
4. Multiple Team Ownership
When no single team can claim end-to-end ownership, accountability fragments. Bugs fall through cracks. Priorities conflict. Code quality degrades as "not my problem" becomes acceptable.
Evidence: Unclear escalation paths, parts of the codebase nobody wants to touch, architectural vision replaced by local expedience.
Amazon's two-pizza rule states that a team should be small enough to be fed with two pizzas (6-10 people). A service that requires more than one two-pizza team to maintain is likely too large. This organizational constraint naturally limits service scope.
Over-decomposition is as dangerous as under-decomposition, and often harder to recognize because each individual service looks simple. Watch for these warning signs:
1. Orchestration Everywhere
When completing a single user action requires choreographing 5, 10, or 20 service calls, you've decomposed too far. The complexity doesn't disappear—it moves into orchestration logic that's harder to test, harder to debug, and harder to make reliable.
Evidence: Saga patterns everywhere, orchestration services that call many downstream services, latency problems from call chains.
2. Distributed Data Nightmares
When data that should be consistent is spread across multiple services, you face eventual consistency challenges for operations that users expect to be immediate. Compensating transactions become the norm.
Evidence: Complex data synchronization jobs, reconciliation processes to fix inconsistencies, user complaints about stale data.
3. Network Overhead Dominates
When a significant portion of response time is spent marshalling data, making network calls, and waiting for responses. Serialization and network hops add latency that users experience.
Evidence: High P95 latency even under low load, tracing shows most time in network rather than computation, optimization efforts focused on reducing call count.
4. Debugging Becomes Archeology
When tracing a single request requires correlating logs from 15 services across multiple teams, debugging simple issues becomes a major investigation. The cognitive load of understanding request flow exceeds the cognitive load of understanding any single service.
Evidence: Simple bugs require hours to diagnose, distributed tracing becomes essential (not optional), nobody can explain full request flows.
5. Changes Cascade Across Services
When a simple feature change requires modifying 5+ services, your boundaries are misaligned with how features are structured. This is the opposite of independent deployability.
Evidence: Most features require multi-service changes, version compatibility matrices, coordinated rollbacks.
6. Anaemic Services
When services contain almost no business logic—they're just data accessors or pass-through proxies. These add latency and operational overhead without providing value.
Evidence: Services that are essentially CRUD wrappers, services that just call other services, services with no meaningful business rules.
Over-decomposition often creates distributed monoliths—services that are technically separate but must be changed, deployed, and scaled together. You get the worst of both worlds: complexity of distribution with none of the independence benefits. This is a common and dangerous failure mode.
Given the tradeoffs, how do you find the right granularity? These principles provide guidance:
Principle 1: Optimize for Independent Deployability
The primary benefit of microservices is independent deployment. A service should be deployable to production without coordinating with other teams or services. If you can't deploy independently, the service boundary isn't providing value.
Test: Can you deploy this service to production today without touching any other service? If not, why not?
Principle 2: Contain Complete Business Capabilities
Each service should deliver a complete business capability, not a fragment of one. Users should be able to describe what the service does in business terms without referencing other services.
Test: Can a domain expert explain what this service does without mentioning other services? If every explanation starts with "It works with Service X to...", the capability is fragmented.
Principle 3: Minimize Synchronous Dependencies
Every synchronous dependency creates temporal coupling and latency. Services should be able to do meaningful work even when dependencies are unavailable (using caching, fallbacks, or async patterns).
Test: Can this service handle a user request if Service X is down for 5 minutes? If the answer is always "no," coupling is too tight.
Principle 4: Align with Team Capacity
Services should match team capacity. A service owned by a two-pizza team shouldn't be larger than that team can understand, maintain, and operate. Conversely, a team shouldn't be split across too many tiny services.
Test: Can every member of the owning team explain the full service in a 10-minute conversation? Does the team have time to maintain all its services properly?
Principle 5: Match Scaling Granularity to Demand
Only decompose for scaling when you have evidence of different scaling needs. Speculative scaling decomposition adds complexity without benefit. Wait until you see actual load patterns.
Test: Does this service have demonstrated scaling requirements that differ from what it would be merged with? "This might need to scale differently" isn't evidence.
Principle 6: Respect Transactional Boundaries
Operations that must be atomic belong in the same service. Splitting across services requires distributed transactions or sagas, adding significant complexity. Keep strongly consistent operations local.
Test: Does any operation in this service require atomic updates to data in another service? If yes, consider merging or restructuring.
Principle 7: Start Bigger, Split When Necessary
It's easier to split a well-structured service than to merge poorly-structured services. Start with coarser granularity and split based on evidence (team contention, scaling needs, independent lifecycles).
Test: Do you have concrete evidence that splitting will solve a real problem, or is it theoretical optimization?
Every split should be justified by specific evidence: 'We need to split because Team A and Team B are blocking each other' or 'The checkout path needs 100x the instances of product browsing.' Vague justifications like 'it's getting too big' or 'microservices best practice' lead to over-decomposition.
While every system is different, these heuristics provide useful starting points:
Team-Size Heuristic
A service should be ownable by 3-8 engineers. Fewer than 3 means the service is probably too small to justify overhead. More than 8 means the service is probably too large for effective ownership.
This isn't prescriptive but observational—it reflects what works in practice at companies with mature microservices.
Complexity Budget Heuristic
Every organization has a "complexity budget"—how much distributed systems complexity it can handle. Each service consumes part of that budget (deployment pipelines, monitoring, debugging tools, operational runbooks). If you're exceeding your complexity budget (services falling through cracks, operations overwhelmed), consolidate.
The Rewrite Heuristic
A service should be small enough that a competent engineer could rewrite it from scratch in 1-2 weeks if the codebase were lost. This limits scope and ensures understandability. If rewriting would take months, the service is too large.
The Focus Heuristic
You should be able to describe what a service does in one sentence without using "and" more than once. "Manages customer orders including cart, checkout, and fulfillment status" is focused. "Handles customers and orders and payments and notifications" is not.
| Metric | Too Small | Reasonable Range | Too Large |
|---|---|---|---|
| Team Size | < 2 people | 3-8 people | 10 people |
| Lines of Code | < 1,000 | 2,000 - 50,000 | 100,000 |
| Database Tables | 1-2 | 5-30 | 50 |
| API Endpoints | 1-3 | 5-30 | 50 |
| Dependencies | N/A | 0-5 synchronous | 10 synchronous |
| Deploy Freq | N/A (blocked) | Weekly+ | Monthly or less |
| Rewrite Time | Hours (why separate?) | 1-2 weeks | Months |
Dependency Fan-Out Heuristic
A service should have no more than 5-7 synchronous dependencies. Beyond this, failure handling becomes intractable, and latency accumulates unacceptably. If you need more, consider:
The Overnight Test
Can the on-call engineer for this service resolve most incidents without calling engineers from other teams? If cross-team escalation is common, either the service is too large (needs decomposition) or services are too coupled (need boundary adjustment).
Reality Check: These Are Guidelines
These heuristics provide reference points, not rules. A 100-line service that encapsulates complex pricing logic is valid. A 100,000-line service that's purely stable infrastructure might be fine. Context matters more than metrics.
Rather than measuring service size directly, measure outcomes: deployment frequency, lead time for changes, mean time to recovery, change failure rate. These metrics reveal whether your granularity is helping or hurting. Right-sized services have high deployment frequency and fast recovery times.
Granularity isn't a one-time decision. Systems evolve, and service boundaries should evolve with them:
The Maturity Curve
Phase 1: Monolith or Macro-Services For new products or small teams, start with a monolith or a few large services. Focus on product-market fit, not architectural purity. You don't have the information or capacity for fine-grained decomposition.
Phase 2: Initial Decomposition As the team grows and domain understanding deepens, identify natural seams. Extract services for areas with different team ownership, scaling needs, or technology requirements. Typically 3-10 services.
Phase 3: Strategic Decomposition With operational maturity and clear domain understanding, decompose strategically. Core domains get fine granularity for maximum flexibility. Generic domains may consolidate for operational simplicity. Typically 10-50 services.
Phase 4: Optimization Continuous adjustment based on evidence. Merge services that don't justify separation. Split services experiencing contention. Granularity becomes dynamic, responding to organizational and technical changes. The number stabilizes based on organization size.
When to Split a Service:
When to Merge Services:
How to Split:
Use the Strangler Fig pattern. New functionality goes to the new service. Gradually migrate existing functionality. Maintain backward compatibility throughout. The old service shrinks over time until eventually decommissioned.
How to Merge:
Choose one service as the target. Migrate functionality from the other. Redirect traffic. Decommission the empty service. Update consumers to call the unified service directly.
Design for reversibility. Use consistent patterns that make splitting and merging easier. Consistent API styles, shared libraries for common concerns, and clear data ownership rules all reduce the cost of adjusting granularity later. The goal isn't to get it right the first time—it's to make adjustment cheap.
DDD provides a principled approach to granularity decisions:
For Core Domains: Finer Granularity
Core domains—where competitive advantage lies—deserve more granularity:
For a financial trading platform, the pricing engine and order matching might each be separate services, even if they're related, because they're core and need maximum flexibility.
For Supporting Domains: Moderate Granularity
Supporting domains need custom solutions but aren't differentiating:
Contractor management for a construction company is supporting—custom-built but not a competitive advantage. One well-designed service might be enough.
For Generic Domains: Coarse Granularity
Generic domains are solved problems:
Email sending, analytics collection, authentication—these might be single services or third-party integrations, not decomposed further.
Aggregate-Based Granularity Check:
DDD aggregates provide a lower bound on granularity. Aggregates that must be transactionally consistent should be in the same service. Splitting an aggregate across services introduces distributed transactions—a massive complexity increase.
Rule: If two things need atomic consistency, they're in the same service. Period.
Bounded Context Alignment:
Bounded contexts provide the natural default granularity. One bounded context ≈ one service. Deviations require justification:
Starting with bounded context = service, then adjusting based on evidence, is the soundest approach.
Technical considerations (lines of code, number of endpoints) are secondary to domain considerations (bounded contexts, business capabilities, team ownership). The domain structure provides the primary guidance; technical metrics help validate decisions.
Service granularity is a tradeoff with costs at both extremes. Finding the right balance requires understanding your domain, team, and operational context. Let's consolidate the key insights:
What's Next:
With principles for defining boundaries and sizing services established, we'll address a critical anti-pattern: the Distributed Monolith. We'll explore how to recognize when decomposition has failed to deliver independence, and strategies for recovery.
You now understand service granularity—how to recognize services that are too big or too small, principles for right-sizing, practical heuristics, and how granularity should evolve over time. Next, we'll tackle the distributed monolith—what happens when decomposition creates coupling instead of independence.