Loading learning content...
Selecting an application architecture is among the most consequential decisions in software development. The choice between monolithic, microservices, or hybrid approaches ripples through every aspect of the system—development velocity, operational complexity, team structure, cost, and ultimately, the product's ability to evolve and scale.
There is no universally "best" architecture. Each approach optimizes for different qualities, and the right choice depends on understanding your specific context: team size and expertise, product requirements, growth trajectory, and organizational constraints.
This page synthesizes the architectural patterns we've explored into a decision framework—providing the analytical tools to evaluate trade-offs, avoid common pitfalls, and make informed choices. Rather than prescribing answers, we'll develop the reasoning skills that enable sound architectural judgment.
The Central Question:
Given our requirements, constraints, team, and trajectory, which architectural approach maximizes our probability of success while minimizing unnecessary complexity and cost?
Answering this question requires honest assessment across multiple dimensions—technical, organizational, and strategic.
By the end of this page, you will understand the key factors influencing architecture selection, frameworks for evaluating trade-offs, common anti-patterns and pitfalls, evolution strategies for changing requirements, and practical guidance for making and documenting architectural decisions.
Architectural decisions must account for multiple interrelated factors. Understanding these factors—and their relative importance for your context—is the foundation of sound decision-making.
1. Team Size and Structure:
Team composition profoundly influences which architectures are viable:
| Team Size | Architectural Implications |
|---|---|
| 1-5 engineers | Monolith strongly preferred. Overhead of distributed systems rarely justified. |
| 5-20 engineers | Monolith or modular monolith. Consider microservices only with specific scaling needs. |
| 20-50 engineers | Modular monolith or selective microservices based on domain boundaries. |
| 50+ engineers | Microservices become more attractive for team autonomy and independent deployment. |
2. Product Maturity:
| Stage | Characteristics | Recommended Approach |
|---|---|---|
| Exploration | Requirements unclear, rapid pivots | Simple monolith. Speed matters most. |
| Growth | Product-market fit found, scaling begins | Modular monolith, prepare for extraction. |
| Scale | High traffic, established features | Selective microservices where needed. |
| Mature | Stable, optimization focus | Architecture matching specific constraints. |
3. Scale Requirements:
Different architectures handle scale differently:
┌─────────────────────────────────────────────────────────────────────────┐
│ Scaling Characteristics │
│ │
│ Monolith: │
│ • Scale by adding instances behind load balancer │
│ • All components scale together (efficient if uniform load) │
│ • Database often becomes bottleneck │
│ • Practical limit: ~100-500 concurrent instances │
│ │
│ Microservices: │
│ • Scale services independently based on specific load │
│ • CPU-heavy service can scale differently from I/O-heavy │
│ • Higher infrastructure overhead per request │
│ • More granular resource optimization possible │
│ │
│ Practical Question: │
│ Do different components have significantly different scaling needs? │
│ If yes → Microservices may help │
│ If no → Monolith scaling is simpler and sufficient │
└─────────────────────────────────────────────────────────────────────────┘
4. Deployment and Release Requirements:
| Requirement | Monolith | Microservices |
|---|---|---|
| Independent feature releases | Feature flags required | Natural with service boundaries |
| Zero-downtime deployments | Rolling updates | Per-service deployments |
| Rollback granularity | Entire application | Individual services |
| Deployment frequency | Limited by coordination | Independent per team |
| Deployment complexity | Simple | Complex orchestration |
5. Domain Complexity:
Microservices align with Domain-Driven Design's bounded contexts:
"Organizations design systems that mirror their own communication structure." Your architecture will reflect your team structure. Trying to build microservices with a monolithic team organization (or vice versa) creates constant friction. Align architecture with organization—or be prepared to change both together.
Apply this systematic framework when evaluating architectural options:
Step 1: Identify Architectural Drivers
List the factors most critical to your system's success:
| Category | Questions to Answer |
|---|---|
| Performance | What are latency requirements? Peak throughput? |
| Scalability | Expected growth? Uneven load across features? |
| Availability | Required uptime? Cost of outage? |
| Modifiability | How often do requirements change? By which teams? |
| Deployability | How frequently must you deploy? Independence needs? |
| Testability | How critical is comprehensive testing? |
| Security | What data is handled? Compliance requirements? |
| Cost | Budget constraints? Operational cost tolerance? |
Step 2: Weight the Drivers
Not all factors matter equally. Assign weights based on your context:
Startup finding product-market fit:
Modifiability: HIGH (requirements change constantly)
Deployability: MEDIUM (speed matters, but less critical)
Scalability: LOW (not yet a problem)
Cost: HIGH (limited runway)
Established e-commerce platform:
Availability: HIGH (downtime = lost revenue)
Scalability: HIGH (Black Friday traffic spikes)
Modifiability: MEDIUM (stable core, new features)
Cost: MEDIUM (optimize, but reliability first)
Step 3: Evaluate Options Against Drivers
Score each architectural option against your weighted drivers:
| Driver (Weight) | Monolith | Modular Monolith | Microservices |
|---|---|---|---|
| Modifiability (High) | 7 | 8 | 9 |
| Deployability (Medium) | 6 | 7 | 9 |
| Operational Cost (High) | 9 | 8 | 5 |
| Development Speed (High) | 9 | 8 | 6 |
| Weighted Score | 31 | 31 | 29 |
Example scoring—your weights and scores will differ based on context.
Step 4: Consider Second-Order Effects
Beyond direct factors, consider ripple effects:
Step 5: Document the Decision
Use Architecture Decision Records (ADRs):
# ADR-001: Adopt Modular Monolith Architecture
## Status: Accepted
## Context
We're building a new e-commerce platform with a team of 12 engineers.
Initial traffic estimates are moderate (1000 req/sec peak).
Product requirements are evolving rapidly as we find market fit.
## Decision
We will build a modular monolith with clear domain boundaries.
## Consequences
+ Faster development velocity (single codebase, simple debugging)
+ Lower operational overhead (one deployment unit)
+ Clear path to extraction if scaling demands emerge
- Must enforce module boundaries through discipline/tooling
- All features scale together (acceptable for current scale)
Favor decisions that are reversible. Starting with a modular monolith lets you extract services later if needed. Starting with microservices is much harder to consolidate. When uncertain, choose the option that preserves future flexibility.
Learn from the mistakes others have made. These anti-patterns recur across organizations attempting architectural transformations.
| Symptom | Possible Cause | Consider |
|---|---|---|
| Deployments require coordination across teams | Distributed monolith | Clarify service boundaries or consolidate |
| Simple features require touching 5+ services | Over-decomposition | Merge related services |
| Test suite takes hours to run | Monolith scaling issues | Modularize or extract slow components |
| Outage in one service cascades everywhere | Missing resilience patterns | Add circuit breakers, bulkheads |
| Engineers spend more time on ops than features | Premature complexity | Simplify architecture |
Architectural mistakes are expensive because they compound. A wrong choice early leads to workarounds, technical debt, and increasingly painful maintenance. The cost of re-architecting later often exceeds the cost of the original system. Invest time in the decision upfront.
Architecture isn't static. As requirements, scale, and teams change, architecture must evolve. Plan for evolution from the beginning.
Monolith to Modular Monolith:
Phase 1: Identify Boundaries
├── Map domain contexts
├── Identify natural seams in code
└── Document dependencies between areas
Phase 2: Establish Modules
├── Create explicit module interfaces
├── Move shared code to common libraries
├── Enforce import rules (linting/build checks)
└── Separate database schemas per module
Phase 3: Validation
├── Each module can be tested independently
├── Changes in one module rarely affect others
└── Teams can work on modules with minimal coordination
Modular Monolith to Microservices:
Extract services incrementally, not all at once:
┌─────────────────────────────────────────────────────────────────────────┐
│ Strangler Fig Pattern │
│ │
│ Original Monolith │
│ ┌─────────────────────────────────┐ │
│ │ Feature A │ Feature B │ C │ │
│ └─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Step 1: Extract Feature A │
│ ┌────────────┐ ┌─────────────────────┐ │
│ │ Service A │ │ Monolith (B + C) │ │
│ └────────────┘ └─────────────────────┘ │
│ │ │ │
│ └───── Facade routes traffic ────┘ │
│ │ │
│ ▼ │
│ Step 2: Extract Feature B │
│ ┌────────────┐ ┌────────────┐ ┌─────────┐ │
│ │ Service A │ │ Service B │ │ Core (C)│ │
│ └────────────┘ └────────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
Each extracted service adds operational overhead. Only extract when the benefits clearly outweigh this cost. A well-structured modular monolith is better than a poorly-structured microservices system. YAGNI (You Aren't Gonna Need It) applies to services too.
Based on the patterns and anti-patterns we've examined, here are practical recommendations for different scenarios:
For Startups (1-10 engineers):
✅ Start with a simple monolith ✅ Focus relentlessly on product, not architecture ✅ Use proven frameworks (Rails, Django, Express, Spring Boot) ✅ Deploy to simple infrastructure (PaaS like Heroku, Railway, or managed Kubernetes) ✅ Establish code quality practices early (testing, code review) ✅ Document major decisions for future reference
❌ Don't adopt microservices because it's trendy ❌ Don't over-engineer for scale you don't have ❌ Don't build custom infrastructure—use managed services
For Growth-Stage (10-50 engineers):
✅ Evolve toward a modular monolith with clear boundaries ✅ Invest in observability (logging, metrics, tracing) ✅ Implement CI/CD with automated testing ✅ Consider extracting specific services when justified ✅ Balance technical investments with product velocity ✅ Begin documenting architecture formally
❌ Don't extract services just to have microservices ❌ Don't ignore operational readiness when extracting ❌ Don't let module boundaries erode over time
| Context | Recommended Start | Evolution Path |
|---|---|---|
| Startup, unclear requirements | Simple monolith | Modularize as domains clarify |
| Small team, clear domain | Modular monolith | Extract if scaling demands |
| Large team, multiple products | Domain-aligned services | Optimize boundaries over time |
| High-scale, proven product | Microservices where needed | Continuous optimization |
| Regulated industry | Based on compliance needs | Document decisions rigorously |
For Scale-Stage (50+ engineers):
✅ Align services with team boundaries (inverse Conway maneuver) ✅ Invest heavily in platform capabilities (CI/CD, observability, service mesh) ✅ Establish architecture review processes ✅ Create internal platforms to reduce service overhead ✅ Maintain clear ownership for every component ✅ Document and communicate architectural principles
❌ Don't let every team create their own standards ❌ Don't ignore the platform/tooling team's needs ❌ Don't skip post-mortems and architectural learning
Universal Recommendations:
| Always | Never |
|---|---|
| Start simpler than you think necessary | Adopt architecture for résumé value |
| Enforce boundaries, even in monoliths | Share databases between services |
| Document significant decisions | Assume requirements won't change |
| Invest in observability | Skip testing for velocity |
| Plan for evolution | Ignore operational requirements |
The best architecture is the one that lets your team ship quality software quickly and sustainably. If your current architecture enables that, it's working. If it's creating friction, evaluate whether the friction is from the architecture itself or from how it's implemented.
We've developed a comprehensive framework for architectural decision-making—understanding not just the technical trade-offs but the organizational, strategic, and evolutionary dimensions of architecture selection.
Module Completion:
With this page, we've completed our exploration of Application Architectures—from the foundational monolithic pattern through distributed microservices, web and mobile backends, to the decision frameworks that guide architectural choices.
You now possess the knowledge to:
This knowledge forms the foundation for the remaining application layer topics—DNS, HTTP, email, and other protocols that implement the communication patterns these architectures require.
Congratulations! You've completed Module 4: Application Architectures. You now understand monolithic, microservices, web, and mobile backend patterns, and possess frameworks for selecting appropriate architectures. This knowledge enables you to design, evaluate, and evolve application layer systems for any requirement.