Make a rigorous, context-driven recommendation on system decomposition — preventing both premature microservices and monoliths that can't scale.
Start with a monolith. Move to services only when you have a specific, demonstrated reason.
Most teams that start with microservices regret it. Most teams that start with a well-structured monolith and extract services when needed succeed. This is not a controversial opinion — it is the consensus of experienced architects.
Score each signal: For microservices (+1) or For monolith (0)
| Signal | Question | Threshold for microservices |
|---|---|---|
| Team size | How many engineers? | >50 engineers, multiple autonomous teams |
| Deploy frequency | How often do parts need independent releases? | Different subsystems deploy >2x/day independently |
| Scale asymmetry | Do different parts need radically different scaling? | Yes — e.g., checkout 100x traffic vs. admin panel |
| Tech heterogeneity | Do different parts need genuinely different tech? | Yes — e.g., ML inference (Python) + OLTP (Go) |
| Fault isolation | Must one part failing not affect others? | Yes — e.g., payments must stay up if recommendations fail |
| Org structure | Do teams already work independently on different domains? | Yes — Conway's Law is real |
| Data isolation | Do different domains have completely separate data? | Yes — clean domain boundaries already exist |
| Current pain | Is the monolith actively causing problems? | Build time >15min, deploys block teams, scaling is impossible |
Score 0–3: Build a monolith
Score 4–5: Build a modular monolith
Score 6–8: Microservices are justified
A single deployable unit. Everything in one codebase.
When to choose:
Structure it well:
src/
├── modules/
│ ├── users/ (self-contained: routes, service, repo)
│ ├── orders/
│ └── payments/
├── shared/ (minimal — only truly shared utilities)
└── main.tsKeep modules loosely coupled internally — this makes future extraction easy.
A single deployable unit with enforced module boundaries. No cross-module direct calls — modules communicate through defined interfaces.
Benefits over a pure monolith:
Benefits over microservices:
modules/
├── users/
│ ├── api/ (public interface — only this is callable from outside)
│ ├── internal/ (private — no other module touches this)
│ └── events/ (events this module emits)
├── orders/
└── payments/Independently deployable services, each with its own data store.
When justified: score ≥ 6 on the signal table above.
Service decomposition principles:
Avoid these decomposition mistakes:
Use Domain-Driven Design to find boundaries:
Example for e-commerce:
✅ Good boundaries:
- Order Service (place, track, cancel orders)
- Inventory Service (stock levels, reservations)
- Payment Service (charge, refund, dispute)
- Notification Service (email, SMS, push)
- User Service (auth, profile, preferences)
❌ Bad boundaries:
- "CRUD Service" (technical, not domain)
- "Database Service" (antipattern)
- Order + Inventory combined (too coupled)| Pattern | Use for | Technology |
|---|---|---|
| Sync request/response | Real-time reads, user-facing queries | REST or gRPC |
| Async events | State changes, cross-domain notifications | Kafka, SQS, RabbitMQ |
| Saga pattern | Distributed transactions across services | Choreography (events) or Orchestration |
Avoid distributed transactions (2PC). Design for eventual consistency where possible.
Always produce:
Wrong to use microservices if:
Wrong to stay monolith if:
181fcbc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.