# Monolith First Design

> Evaluates architecture decisions to determine when a monolithic application should be chosen over microservices using team assessment, domain complexity analysis, operational cost modeling, and weighted scoring frameworks.

- Skill: `paulpas/monolith-first-design` (Agent Skill)
- Install (CLI): `npx skillmds@latest add paulpas/monolith-first-design`
- Raw SKILL.md: https://api.skillmd.com/api/skills/paulpas/monolith-first-design/raw
- Safety review: PASS (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: paulpas (https://skillmd.com/u/paulpas)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/paulpas/monolith-first-design

---






# Monolith-First Architecture Decision Framework

Evaluates whether a monolithic application is the correct starting architecture over distributed microservices. Applies team assessment, domain complexity analysis, operational cost modeling, and weighted decision scoring to produce a data-driven recommendation with explicit decomposition exit criteria.

## TL;DR Checklist

- [ ] Assess team size, experience, and on-call coverage before considering services
- [ ] Map bounded contexts and measure coupling — only decompose if high internal cohesion exists
- [ ] Quantify operational overhead: infrastructure hours, deployment complexity, testing matrix explosion
- [ ] Compare real-world case studies relevant to your domain and scale
- [ ] Compute weighted architecture decision score using the framework below
- [ ] Define explicit exit criteria for future decomposition before committing to monolith

---

## When to Use

Use this skill when:

- A team is evaluating architecture options for a new application or major feature set
- Engineering leadership is debating microservices vs monolith and needs data-driven arguments
- You need to push back against "microservices by default" pressure from consultants or stakeholders
- The team has fewer than 10 engineers, lacks platform/SRE support, or has under 2 years of collective experience
- An existing monolith is being considered for decomposition and you need structured evaluation criteria
- A startup or small team needs to ship a product quickly with limited operational bandwidth

## When NOT to Use

Avoid this skill for:

- Teams that have already decomposed and need guidance on running services (use `monolith-scaling-strategies` instead)
- Large organizations (50+ engineers) that already have mature platform teams and SRE organizations
- High-scale distributed systems requiring geographic redundancy, multi-region active-active deployment
- Regulatory requirements mandating isolated data processing per tenant (use regulated microservices pattern)
- Post-decomposition refactoring of an existing monolith into services (use `monolith-refactoring`)

---

## Core Workflow

### Phase 1: Team Readiness Assessment

1. **Gather team composition data** — Count engineers by role (backend, frontend, devops, QA). Interview leads about on-call rotation experience and incident response capability. **Checkpoint:** Record exact headcount per function; estimates invalidate the scoring below.

2. **Evaluate operational maturity** — Assess whether the team has: dedicated SRE/platform engineering, CI/CD pipeline ownership, centralized logging/metrics infrastructure, and documented runbooks. Score 0-4 for each dimension. **Checkpoint:** If average operational score is below 1.5, microservices are almost certainly premature regardless of domain complexity.

3. **Compute team readiness score** — Apply the `assess_team_readiness()` function below with weighted factors. Teams scoring below 60 on a 0-100 scale should strongly favor monolith architecture.

4. **Document assumptions** — Record every assumption about future team growth, hiring plans, and organizational changes that could shift the recommendation over time. **Checkpoint:** Any unrecorded assumption becomes an untested risk during decomposition decisions later.

### Phase 2: Domain Complexity Analysis

5. **Identify bounded contexts** — Work with product stakeholders to map business capabilities. Each bounded context represents a distinct area of domain knowledge with well-defined interfaces to other contexts. Use event storming sessions or domain-driven design workshops. **Checkpoint:** If you cannot list at least 3-5 distinct bounded contexts, the domain is not complex enough for decomposition.

6. **Calculate inter-context coupling** — For each pair of bounded contexts, quantify: shared data entities, synchronous API calls per day, event frequency, and business process overlap. High coupling (score above threshold defined in `calculate_coupling()`) indicates the contexts should remain in one codebase. **Checkpoint:** Average coupling score below 0.4 means a single service could handle all contexts efficiently.

7. **Evaluate data independence** — Determine whether each bounded context can own its data store without cross-context joins or transactional consistency requirements. Monoliths excel at multi-table ACID transactions; microservices require sagas, CQRS, and eventual consistency. **Checkpoint:** If more than 30% of business workflows require cross-context transactions, the monolith is structurally superior for data integrity.

8. **Map change frequency by context** — Analyze Git history (last 6 months) to determine which bounded contexts are modified most frequently together on the same commits. High co-change frequency indicates tight coupling regardless of API boundaries. **Checkpoint:** If top-5 most-changed contexts share over 60% of their commit authors, decomposing them would increase coordination overhead.

### Phase 3: Operational Overhead Quantification

9. **Model monolith operational costs** — Calculate infrastructure hours per deployment, testing matrix size (N services × N environments), CI/CD pipeline complexity, monitoring instrumentation effort, and debugging time per incident. Use `calculate_operational_costs()` to produce quantitative comparisons. **Checkpoint:** Include both direct engineering hours AND opportunity cost of delayed feature delivery.

10. **Model microservices operational costs** — Add service discovery complexity, inter-service communication reliability (network partitions, retries, circuit breakers), distributed tracing overhead, data consistency patterns (sagas, CQRS), deployment coordination across teams, and infrastructure as code maintenance per service. **Checkpoint:** Each additional service adds approximately 15-20% operational overhead to the team's total capacity.

11. **Compare total cost of ownership** — Run the comparison for 3-month, 6-month, and 12-month horizons. Monoliths typically show lower costs through month 9-18; microservices cross-over depends on team size and domain complexity. **Checkpoint:** The break-even point must be within your product's expected lifetime, or the monolith is the rational choice.

### Phase 4: Decision Synthesis

12. **Compute weighted architecture decision score** — Apply `synthesize_architecture_decision()` with the team readiness score, coupling metrics, operational cost deltas, and strategic factors. Scores above 65 strongly recommend monolith; below 35 suggest microservices may be justified. **Checkpoint:** The recommendation must include the single highest-impact risk factor to monitor.

13. **Document exit criteria** — Regardless of recommendation, define measurable conditions that would trigger a decomposition review. Examples: team exceeds 12 engineers in a specific domain, coupling score rises above 0.7 for any context pair, deployment frequency drops below weekly due to coordination. **Checkpoint:** Exit criteria must be quarterly-reviewable with objective measurements — no subjective "feels slow" thresholds.

---

## Real-World Case Studies

### Shopify (Early Days)
Shopify started as a monolith in Ruby on Rails and maintained it for years while scaling to process billions in GMV. Key insight: they extracted services only when specific bottlenecks were measurable — checkout, search, and product catalog each became independent services after the monolith proved it could handle baseline traffic with team efficiency.

**Lesson:** Measure first, extract second. The monolith handled years of growth before any decomposition was needed.

### WordPress.com
Automattic runs millions of WordPress sites from a single codebase deployed across thousands of servers. They use horizontal scaling and sharding rather than decomposing the application. Their architecture decision preserved developer velocity for over a decade.

**Lesson:** Horizontal scaling within a monolith can handle extreme load when vertical complexity is low.

### Basecamp (37signals)
Basecamp has consistently shipped feature-complete applications as monoliths while competitors chased microservice architectures. Their philosophy: "The cost of building the plumbing is higher than the cost of writing the app." Each team member can understand the entire codebase in weeks, not months.

**Lesson:** Developer comprehension of the full system is a competitive advantage that decomposes with services.

### Amazon's API Mandate
Amazon's famous 2002 mandate requiring all teams to expose data and functionality through service interfaces was reactive — born from operational pain after years of microservice experimentation. Before the mandate, teams built tightly coupled distributed systems with no standards. The API mandate standardized the chaos; it didn't create the microservices architecture.

**Lesson:** Even Amazon validated monolith-first through failure. The mandate was about governing decomposition, not justifying it.

---

## Failure Modes

### 1. God Module
When a monolith grows beyond 500k lines without domain boundaries, no engineer can understand it end-to-end. **Mitigation:** Enforce module-level import restrictions, require design reviews for cross-module changes, and maintain explicit bounded context diagrams that are treated as architectural contracts.

### 2. Distributed Monolith
Decomposing a tightly coupled system into services while keeping synchronous RPC calls between every component creates the worst of both worlds: network latency plus deployment complexity without any independence. **Mitigation:** If coupling score is above 0.5 for more than two context pairs, decomposing would likely produce a distributed monolith — stay in the monolith and refactor internal boundaries first.

### 3. Database Coupling
When multiple logical services share tables or rely on foreign key constraints, each service cannot deploy independently without coordinating schema changes. **Mitigation:** In the monolith phase, design data access patterns so that each bounded context has its own schema namespace (even within a shared database). This makes future extraction cleaner.

### 4. Shared Global State
Singletons, global caches, and shared mutable state make it impossible to deploy services independently or run multiple instances without conflicts. **Mitigation:** Apply the dependency injection pattern consistently from day one. Every shared resource must be injectable, not globally accessible. This preserves future extraction paths.

### 5. No Extraction Strategy
Starting as a monolith without any plan for how specific modules will become services later means every future decomposition is a re-implementation. **Mitigation:** Even in the monolith phase, structure code so that bounded context boundaries are explicit: separate packages/modules per context, no cross-context imports beyond well-defined interfaces, and context-specific data models.

---

## Implementation Patterns

### Pattern 1: Team Readiness Assessment

Quantifies whether a team has the operational maturity to support microservices. Returns a score from 0-100 where scores below 60 indicate the team should strongly favor a monolithic architecture.

```python
from dataclasses import dataclass, field
from typing import List


@dataclass
class TeamReadiness:
    """Team readiness assessment result with breakdown by dimension."""
    
    total_score: float = 0.0
    backend_engineers: int = 0
    frontend_engineers: int = 0
    devops_engineers: int = 0
    qa_engineers: int = 0
    operational_maturity_avg: float = 0.0
    recommendation: str = ""


def assess_team_readiness(
    team_size: int,
    backend_count: int,
    frontend_count: int,
    devops_count: int,
    qa_count: int,
    has_dedicated_sre: bool,
    has_ci_cd_pipeline: bool,
    has_centralized_logging: bool,
    has_documented_runbooks: bool,
    avg_incident_response_minutes: float,
) -> TeamReadiness:
    """Assess team readiness for microservices decomposition.
    
    Evaluates team composition and operational maturity on a 0-100 scale.
    Scores below 60 indicate the team should strongly favor monolith architecture.
    
    Args:
        team_size: Total number of engineers on the product team.
        backend_count: Number of backend-focused engineers.
        frontend_count: Number of frontend-focused engineers.
        devops_count: Number of DevOps/SRE/platform engineers.
        qa_count: Number of quality assurance engineers.
        has_dedicated_sre: Whether the team has dedicated SRE or platform engineering.
        has_ci_cd_pipeline: Whether the team owns and maintains their CI/CD pipeline.
        has_centralized_logging: Whether centralized logging/metrics infrastructure exists.
        has_documented_runbooks: Whether incident response runbooks are documented.
        avg_incident_response_minutes: Average time to detect and begin responding to incidents.
    
    Returns:
        TeamReadiness with total score, breakdowns, and recommendation string.
    
    Raises:
        ValueError: If any count is negative or team_size does not match sum of roles.
    """
    if any(c < 0 for c in [backend_count, frontend_count, devops_count, qa_count]):
        raise ValueError("Engineer counts cannot be negative")
    
    role_sum = backend_count + frontend_count + devops_count + qa_count
    if team_size != 0 and role_sum > team_size:
        raise ValueError(f"Role counts ({role_sum}) exceed team size ({team_size})")
    
    # Team size penalty: microservices require coordination overhead
    # Teams under 6 engineers should almost always use monolith
    if team_size < 3:
        size_score = 0.0
    elif team_size < 6:
        size_score = 25.0
    elif team_size < 10:
        size_score = 50.0
    else:
        size_score = 75.0
    
    # Operational maturity scoring (each dimension worth up to 15 points)
    operational_scores = [
        (has_dedicated_sre, 15),
        (has_ci_cd_pipeline, 15),
        (has_centralized_logging, 15),
        (has_documented_runbooks, 15),
    ]
    
    op_maturity = sum(
        points if enabled else 0
        for enabled, points in operational_scores
    )
    
    # Incident response speed penalty: slow response indicates immature operations
    if avg_incident_response_minutes > 60:
        incident_penalty = 15.0
    elif avg_incident_response_minutes > 30:
        incident_penalty = 7.5
    else:
        incident_penalty = 0.0
    
    # DevOps ratio check: microservices need strong platform support
    if team_size > 0:
        devops_ratio = devops_count / team_size
    else:
        devops_ratio = 0.0
    
    platform_score = min(20.0, devops_ratio * 100.0)
    
    # Weighted total
    total_score = (
        size_score * 0.30 +          # Team composition weight
        op_maturity * 0.35 +          # Operational maturity weight  
        (15 - incident_penalty) * 0.20 +  # Incident response quality
        platform_score * 0.15         # Platform engineering support
    )
    
    total_score = round(min(100.0, max(0.0, total_score)), 1)
    
    # Recommendation logic
    if total_score < 35:
        recommendation = "Strongly favor monolith — team lacks operational maturity"
    elif total_score < 60:
        recommendation = "Favor monolith — microservices introduce unmanageable complexity"
    elif total_score < 75:
        recommendation = "Monolith recommended; re-evaluate at next growth phase"
    else:
        recommendation = "Microservices may be justified if domain coupling supports it"
    
    return TeamReadiness(
        total_score=total_score,
        backend_engineers=backend_count,
        frontend_engineers=frontend_count,
        devops_engineers=devops_count,
        qa_engineers=qa_count,
        operational_maturity_avg=round(op_maturity / 4.0, 1),
        recommendation=recommendation,
    )
```

### Pattern 2: Domain Analysis with Bounded Contexts and Coupling

Analyzes domain boundaries to determine if decomposition is warranted by measuring coupling between bounded contexts. High coupling indicates the system should remain monolithic.

```python
from dataclasses import dataclass, field
from typing import Dict, List, Tuple


@dataclass
class CouplingMeasurement:
    """Measures coupling between two bounded contexts."""
    
    context_a: str = ""
    context_b: str = ""
    shared_entities: int = 0
    sync_api_calls_per_day: int = 0
    event_frequency_per_day: int = 0
    business_process_overlap: float = 0.0  # 0.0 to 1.0
    
    @property
    def coupling_score(self) -> float:
        """Compute coupling score from 0.0 (independent) to 1.0 (tightly coupled)."""
        entity_weight = min(0.3, self.shared_entities * 0.05)
        sync_weight = min(0.4, self.sync_api_calls_per_day / 200.0) if self.sync_api_calls_per_day > 0 else 0.0
        event_weight = min(0.15, self.event_frequency_per_day / 500.0) if self.event_frequency_per_day > 0 else 0.0
        process_weight = self.business_process_overlap * 0.3
        
        score = entity_weight + sync_weight + event_weight + process_weight
        return round(min(1.0, max(0.0, score)), 3)


@dataclass
class DomainAnalysis:
    """Results of bounded context and coupling analysis."""
    
    contexts: List[str] = field(default_factory=list)
    coupling_matrix: Dict[Tuple[str, str], CouplingMeasurement] = field(default_factory=dict)
    average_coupling: float = 0.0
    max_coupling_pair: Tuple[str, str, float] = ("", "", 0.0)
    recommendation: str = ""


def calculate_coupling(
    bounded_contexts: List[str],
    entity_shares: Dict[Tuple[str, str], int],
    api_calls_per_day: Dict[Tuple[str, str], int],
    event_frequencies: Dict[Tuple[str, str], int],
    process_overlaps: Dict[Tuple[str, str], float],
) -> DomainAnalysis:
    """Calculate coupling between bounded contexts to assess decomposition feasibility.
    
    For each pair of contexts, measures shared data entities, synchronous API usage,
    event-driven communication frequency, and business process overlap. Produces a
    composite coupling score where scores above 0.5 indicate the contexts should remain
    in one codebase until coupling is reduced through internal refactoring.
    
    Args:
        bounded_contexts: List of domain context names (e.g., ["orders", "payments", "inventory"]).
        entity_shares: Maps context pairs to count of shared database entities/tables.
        api_calls_per_day: Maps context pairs to daily synchronous API call volume.
        event_frequencies: Maps context pairs to daily event/message frequency.
        process_overlaps: Maps context pairs to business process overlap ratio (0.0-1.0).
    
    Returns:
        DomainAnalysis with full coupling matrix and recommendation.
    
    Raises:
        ValueError: If contexts list is empty or pair keys are inconsistent.
    """
    if not bounded_contexts:
        raise ValueError("Must provide at least one bounded context")
    
    if len(bounded_contexts) < 2:
        return DomainAnalysis(
            contexts=bounded_contexts,
            recommendation="Single context — monolith is trivially correct",
        )
    
    coupling_matrix: Dict[Tuple[str, str], CouplingMeasurement] = {}
    coupling_scores: List[float] = []
    max_pair: Tuple[str, str, float] = ("", "", 0.0)
    
    # Build symmetric coupling matrix (only store upper triangle)
    for i in range(len(bounded_contexts)):
        for j in range(i + 1, len(bounded_contexts)):
            ctx_a = bounded_contexts[i]
            ctx_b = bounded_contexts[j]
            pair_key = (ctx_a, ctx_b)
            
            shared = entity_shares.get(pair_key, 0)
            calls = api_calls_per_day.get(pair_key, 0)
            events = event_frequencies.get(pair_key, 0)
            overlap = process_overlaps.get(pair_key, 0.0)
            
            measurement = CouplingMeasurement(
                context_a=ctx_a,
                context_b=ctx_b,
                shared_entities=shared,
                sync_api_calls_per_day=calls,
                event_frequency_per_day=events,
                business_process_overlap=overlap,
            )
            
            coupling_matrix[pair_key] = measurement
            score = measurement.coupling_score
            coupling_scores.append(score)
            
            if score > max_pair[2]:
                max_pair = (ctx_a, ctx_b, score)
    
    avg_coupling = round(sum(coupling_scores) / len(coupling_scores), 3) if coupling_scores else 0.0
    
    # Recommendation logic based on coupling thresholds
    if avg_coupling < 0.2:
        recommendation = (
            f"Low coupling ({avg_coupling}) — contexts could potentially be separate services, "
            f"but monolith is still preferred for teams under 10 engineers."
        )
    elif avg_coupling < 0.4:
        recommendation = (
            f"Moderate coupling ({avg_coupling}) — remain monolithic. "
            f"Decompose only after internal boundaries are strengthened."
        )
    elif max_pair[2] > 0.5:
        recommendation = (
            f"High coupling between '{max_pair[0]}' and '{max_pair[1]}' ({max_pair[2]:.3f}). "
            f"Decomposing these contexts would produce a distributed monolith. "
            f"Refactor internal boundaries first."
        )
    else:
        recommendation = (
            f"Elevated average coupling ({avg_coupling}) — defer decomposition until "
            f"coupling drops below 0.4 through domain-driven refactoring."
        )
    
    return DomainAnalysis(
        contexts=bounded_contexts,
        coupling_matrix=coupling_matrix,
        average_coupling=avg_coupling,
        max_coupling_pair=max_pair,
        recommendation=recommendation,
    )
```

### Pattern 3: Operational Cost Comparison Calculator

Models the total operational cost of monolith vs microservices across multiple dimensions and time horizons.

```python
from dataclasses import dataclass
from typing import List


@dataclass
class OperationalCostComparison:
    """Total operational cost comparison between monolith and microservices."""
    
    weeks_modeled: int = 12
    team_size: int = 4
    
    # Monolith costs (per week)
    monolith_infrastructure_hours: float = 8.0
    monolith_deployment_hours: float = 2.0
    monolith_testing_hours: float = 6.0
    monolith_debugging_hours: float = 3.0
    monolith_total_per_week: float = 0.0
    
    # Microservices costs (per week)
    microservice_infrastructure_hours: float = 0.0
    microservice_deployment_hours: float = 0.0
    microservice_testing_hours: float = 0.0
    microservice_debugging_hours: float = 0.0
    microservice_coordination_hours: float = 0.0
    microservice_total_per_week: float = 0.0
    
    # Delta and break-even
    weekly_hour_savings_monolith: float = 0.0
    break_even_weeks: int = 0
    recommendation: str = ""


def calculate_operational_costs(
    team_size: int,
    num_microservices_target: int,
    avg_engineer_hourly_rate: float,
    deployments_per_week_monolith: int = 3,
    deployments_per_week_services: int = 12,
    test_env_count: int = 3,
    monitoring_hours_per_service: float = 4.0,
    coordination_meeting_hours_per_week: float = 8.0,
) -> OperationalCostComparison:
    """Calculate and compare operational costs between monolith and microservices architectures.
    
    Models infrastructure management hours, deployment overhead, testing complexity,
    debugging time across services, and team coordination costs over a configurable
    time horizon. Produces a weekly cost delta and estimated break-even point.
    
    Args:
        team_size: Total number of engineers on the product team.
        num_microservices_target: Number of services if decomposing.
        avg_engineer_hourly_rate: Blended hourly rate for engineering labor (USD).
        deployments_per_week_monolith: Expected deployment frequency for monolith.
        deployments_per_week_services: Expected combined deployment frequency across all services.
        test_env_count: Number of environment configurations to maintain.
        monitoring_hours_per_service: Weekly hours per service for metrics/alert tuning.
        coordination_meeting_hours_per_week: Hours spent on cross-service coordination meetings.
    
    Returns:
        OperationalCostComparison with weekly totals, deltas, break-even, and recommendation.
    
    Raises:
        ValueError: If team_size is below 1 or num_microservices_target is below 2.
    """
    if team_size < 1:
        raise ValueError("Team size must be at least 1")
    if num_microservices_target < 2:
        raise ValueError("Microservice target must be at least 2 for meaningful comparison")
    
    # === MONOLITH COSTS (per week) ===
    monolith_infra = 4.0  # Base infrastructure management
    monolith_deploy = deployments_per_week_monolith * 0.5  # ~30 min per deploy
    monolith_testing = test_env_count * 1.5  # Setup and smoke tests across environments
    monolith_debugging = 2.0  # Single codebase, straightforward stack traces
    
    monolith_total = monolith_infra + monolith_deploy + monolith_testing + monolith_debugging
    
    # === MICROSERVICES COSTS (per week) ===
    microservice_infra = (
        num_microservices_target * 2.0            # Per-service infra management
        + monitoring_hours_per_service * num_microservices_target  # Alert tuning per service
    )
    
    microservice_deploy = deployments_per_week_services * 0.5  # Coordination overhead
    microservice_testing = (
        test_env_count * num_microservices_target * 1.5  # N environments × N services
    )
    microservice_debugging = 3.0 * num_microservices_target  # Distributed tracing overhead
    microservice_coordination = coordination_meeting_hours_per_week
    
    microservice_total = (
        microservice_infra + microservice_deploy + microservice_testing
        + microservice_debugging + microservice_coordination
    )
    
    weekly_savings = round(microservice_total - monolith_total, 1)
    
    # Break-even calculation: how many weeks of savings until microservice infrastructure investment pays off
    # Assume upfront microservice platform setup costs 40 engineering-hours
    upfront_investment_hours = 40.0
    break_even_weeks = (
        int(upfront_investment_hours / weekly_savings) if weekly_savings > 0 else 999
    )
    
    # Recommendation based on team size and cost delta
    if weekly_savings < 10:
        recommendation = (
            f"Monolith saves ~{weekly_savings:.0f} engineering-hours/week. "
            f"With {team_size} engineers, this equals ${(weekly_savings * avg_engineer_hourly_rate):,.0f}/week in labor savings. "
            f"Break-even at week {break_even_weeks}. Monolith is the rational choice."
        )
    elif weekly_savings < 25:
        recommendation = (
            f"Marginal savings ({weekly_savings:.0f} hours/week) — insufficient to justify "
            f"the architectural complexity of microservices for a team of {team_size} engineers. "
            f"Re-evaluate when team grows beyond 12 engineers."
        )
    else:
        recommendation = (
            f"Microservices save ~{weekly_savings:.0f} hours/week. "
            f"Break-even at week {break_even_weeks}. "
            f"Decomposition may be justified if team exceeds 10 engineers "
            f"and domain coupling supports independent service boundaries."
        )
    
    return OperationalCostComparison(
        team_size=team_size,
        weeks_modeled=12,
        monolith_total_per_week=round(monolith_total, 1),
        microservice_total_per_week=round(microservice_total, 1),
        weekly_hour_savings_monolith=weekly_savings,
        break_even_weeks=min(break_even_weeks, 999),
        recommendation=recommendation,
    )
```

### Pattern 4: Architecture Decision Synthesis with Weighted Scoring

Combines all assessment dimensions into a single weighted score that drives the final architecture recommendation.

```python
from dataclasses import dataclass
from typing import Dict, List


@dataclass
class ArchitectureDecision:
    """Complete architecture decision output."""
    
    recommendation: str = ""  # "monolith", "microservices", or "hybrid"
    confidence_score: float = 0.0  # 0-100, higher = more confident in recommendation
    
    # Dimension scores (each 0-100)
    team_readiness_score: float = 0.0
    domain_coupling_score: float = 0.0
    operational_cost_score: float = 0.0
    strategic_alignment_score: float = 0.0
    
    # Weights applied to each dimension
    weight_team: float = 0.30
    weight_coupling: float = 0.25
    weight_operations: float = 0.25
    weight_strategy: float = 0.20
    
    # Risk factors and exit criteria
    highest_risk_factor: str = ""
    exit_criteria: List[str] = field(default_factory=list)


def synthesize_architecture_decision(
    team_readiness_score: float,
    average_coupling: float,
    weekly_hour_savings_monolith: float,
    team_size: int,
    product_lifespan_months: int = 24,
    has_strategic_need_for_isolation: bool = False,
    regulatory_tenant_isolation: bool = False,
) -> ArchitectureDecision:
    """Synthesize architecture decision from all assessment dimensions.
    
    Combines team readiness, domain coupling, operational cost analysis, and strategic
    factors into a single weighted recommendation. Produces the final monolith-vs-microservices
    decision with explicit risk factors and decomposition exit criteria.
    
    Args:
        team_readiness_score: Score from assess_team_readiness() on 0-100 scale.
        average_coupling: Average coupling score from calculate_coupling() (0.0-1.0).
        weekly_hour_savings_monolith: Hour savings for monolith from calculate_operational_costs().
            Positive = monolith saves time; negative = microservices save time.
        team_size: Total number of engineers on the product team.
        product_lifespan_months: Expected months until major rewrite or decommissioning.
        has_strategic_need_for_isolation: Whether business requirements demand service isolation.
        regulatory_tenant_isolation: Whether regulations require data/processing separation per tenant.
    
    Returns:
        ArchitectureDecision with recommendation, confidence score, dimension scores,
        highest risk factor, and measurable exit criteria for future decomposition.
    
    Raises:
        ValueError: If any score is outside its expected range.
    """
    # Validate input ranges
    if not (0 <= team_readiness_score <= 100):
        raise ValueError(f"team_readiness_score must be 0-100, got {team_readiness_score}")
    if not (0 <= average_coupling <= 1.0):
        raise ValueError(f"average_coupling must be 0.0-1.0, got {average_coupling}")
    
    # === Convert raw metrics to normalized dimension scores (0-100) ===
    
    # Team readiness maps directly: lower score = stronger monolith case
    team_score = round(100 - team_readiness_score, 1)
    
    # Coupling: high coupling strongly favors monolith (inverse relationship)
    coupling_score = round((1.0 - average_coupling) * 100, 1)
    
    # Operational cost: positive savings for monolith favors it
    if weekly_hour_savings_monolith > 20:
        ops_score = 95.0
    elif weekly_hour_savings_monolith > 10:
        ops_score = 75.0 + (weekly_hour_savings_monolith - 10) * 2.0
    elif weekly_hour_savings_monolith > 0:
        ops_score = 55.0 + weekly_hour_savings_monolith * 2.0
    else:
        # Microservices save time — but only if team is large enough to benefit
        ops_score = max(5.0, 45.0 + (team_size - 6) * 5.0) if team_size > 6 else 15.0
    
    # Strategic factors
    strategy_score = 20.0  # Default: favors monolith
    if regulatory_tenant_isolation:
        strategy_score = 5.0  # Regulation mandates isolation regardless of other factors
    elif has_strategic_need_for_isolation:
        strategy_score = 30.0  # Some business case for isolation
    
    # === Weighted composite score (higher = more monolith, lower = more microservices) ===
    weighted_score = (
        team_score * 0.30 +
        coupling_score * 0.25 +
        ops_score * 0.25 +
        strategy_score * 0.20
    )
    weighted_score = round(min(100.0, max(0.0, weighted_score)), 1)
    
    # === Determine recommendation and confidence ===
    if weighted_score >= 65:
        recommendation = "monolith"
        confidence = min(95.0, weighted_score * 0.85)
    elif weighted_score <= 35:
        recommendation = "microservices"
        confidence = min(90.0, (100 - weighted_score) * 0.85)
    else:
        recommendation = "hybrid-monolith-start"
        confidence = min(70.0, (abs(weighted_score - 50)) * 2.0)
    
    # === Identify highest risk factor ===
    dimension_scores = {
        "team operational maturity": team_readiness_score,
        "domain coupling (high=bad for decomposition)": average_coupling * 100,
        "operational cost delta": abs(weekly_hour_savings_monolith),
        "strategic isolation needs": 100 - strategy_score if not regulatory_tenant_isolation else 50,
    }
    
    highest_risk = max(dimension_scores, key=dimension_scores.get)
    
    # === Generate exit criteria for future decomposition ===
    exit_criteria = [
        f"Team grows beyond {min(team_size + 8, 20)} engineers with dedicated SRE platform support",
        f"Any bounded context pair coupling exceeds 0.5 sustained over two quarterly reviews",
        f"Deployment coordination overhead exceeds 15 hours/week across teams",
        f"Feature delivery velocity drops below 2 significant releases per sprint for 3+ consecutive quarters",
        f"A specific bounded context demonstrates independent scaling requirements "
        f"(e.g., traffic patterns 5x others) sustained for 6 months",
    ]
    
    # Add strategic exit criteria if applicable
    if product_lifespan_months > 36:
        exit_criteria.append(
            "Product lifespan exceeds 36 months — schedule annual architecture review"
        )
    
    return ArchitectureDecision(
        recommendation=recommendation,
        confidence_score=round(confidence, 1),
        team_readiness_score=team_score,
        domain_coupling_score=coupling_score,
        operational_cost_score=ops_score,
        strategic_alignment_score=strategy_score,
        highest_risk_factor=highest_risk,
        exit_criteria=exit_criteria,
    )
```

---

## Constraints

### MUST DO
- Always assess team operational maturity before domain complexity — a weak team cannot run microservices regardless of architecture quality
- Quantify every claim with measurements: coupling scores, deployment hours, incident response times
- Document assumptions about future team growth and organizational changes in the decision record
- Define explicit exit criteria that are quarterly-reviewable with objective thresholds — no subjective language
- Structure the monolith with explicit bounded context boundaries (separate modules/packages per context) from day one
- Treat the architecture decision as reversible: write it down, set review dates, measure against criteria
- Include all stakeholders (product, engineering, operations) in the assessment — architecture decisions affect delivery speed and reliability for all teams
- When recommending microservices, require that the team has at least 3 DevOps/SRE engineers or a dedicated platform team

### MUST NOT DO
- Base architecture decisions on conference talks, vendor presentations, or "industry trends" without quantitative analysis
- Decompose based on technology preferences (e.g., "everyone wants to use Kafka") — decompose based on domain boundaries and coupling metrics
- Assume microservices automatically improve scalability — they shift complexity from code to coordination without necessarily reducing either
- Skip the operational cost comparison — many teams dramatically underestimate debugging, monitoring, and deployment overhead across services
- Set exit criteria that are impossible to measure objectively (e.g., "when it feels slow") — every criterion must have a quantifiable threshold
- Ignore the distributed monolith anti-pattern — if coupling is high, decomposing creates synchronous RPC chains with none of the independence benefits
- Allow architecture decisions to be made by a single individual without engineering leadership consensus and documented tradeoffs

---

## Output Template

When applying this skill, produce:

1. **Team Readiness Assessment** — Team composition data, operational maturity scores, readiness score (0-100), and recommendation with rationale
2. **Domain Analysis Report** — Bounded context inventory, coupling matrix with scores per pair, average coupling, highest-risk pair, and decomposition feasibility assessment
3. **Operational Cost Comparison** — Weekly engineering hours for monolith vs microservices across all dimensions, total weekly delta, cost in labor dollars, break-even timeline
4. **Architecture Decision** — Final recommendation (monolith / microservices / hybrid), confidence score (0-100), weighted dimension breakdown with individual scores and applied weights
5. **Risk Assessment** — Highest-impact risk factor to monitor, secondary risks ranked by probability and impact, mitigation strategies for each
6. **Exit Criteria** — Measurable conditions that would trigger a decomposition review, including thresholds, review cadence (quarterly recommended), and responsible party

---

## Related Skills

| Skill | Purpose |
|---|---|
| `monolith-architecture` | Designing and implementing the monolith structure with bounded context boundaries |
| `monolith-refactoring` | Extracting services from a mature monolith when exit criteria are met |
| `monolith-scaling-strategies` | Scaling patterns for monoliths: read replicas, sharding, caching, async queues |

> 📖 skill(local cache): monolith-first-design

## Live References

> Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.

- [Martin Fowler — Modular Monolith](https://martinfowler.com/articles/modular-monolith.html)
- [Martin Fowler — Scaling the Monolith](https://martinfowler.com/articles/scaling-monolith.html)
- [Shopify Engineering Blog — Scaling Ruby on Rails](https://shopify.engineering/tagged/monolith)
- [Basecamp's Approach to Productivity and Architecture](https://basecamp.com/essays/the-cost-of-microservices)
- [Amazon's API Mandate — Case Study by Werner Vogels](https://martinfowler.com/articles/201901-monolith-to-microservices.html)

