# Microservices Architecture

A monolithic application becomes a liability at a specific, measurable point: when the cost of coordination between development teams exceeds the cost of distributed systems complexity. For most or...

## Microservices Architecture: When to Split the Monolith (And When Not To)

Microservices architecture design, monolith migration, service boundary definition, Kubernetes deployment, and distributed systems observability — from a Zeeland, MI company with 20+ years building enterprise software for manufacturers and complex organizations. We help you decompose intelligently, not just decompose.

---

## Our Process

1. **Architecture Assessment & Decomposition Planning (2-3 Weeks)** — We audit your existing monolith: codebase structure, database schema, deployment pipeline, team topology, and pain points. We map the domain into bounded contexts, identify candidate services for extraction, and assess the coupling between them. We also evaluate whether microservices are the right solution at all — in some cases, a modular monolith refactor delivers better ROI. Deliverable: a decomposition roadmap with recommended extraction order, risk assessment per service boundary, and infrastructure requirements with cost estimates. This assessment also covers your existing cloud migration posture and CI/CD pipelines.
2. **Infrastructure Foundation: Containers, Orchestration & CI/CD (2-4 Weeks)** — Before extracting the first service, we build the platform it will run on. Docker containerization of the existing monolith (so it runs in the same environment as future services), Kubernetes cluster provisioning with infrastructure as code (Terraform or Pulumi), CI/CD pipeline configuration for automated build, test, and deploy per service, and container registry setup. We also deploy the observability stack — Prometheus, Grafana, Jaeger, and centralized logging — so monitoring is in place before the first service goes live.
3. **First Service Extraction: Strangler Fig (3-6 Weeks)** — We extract the first service using the Strangler Fig pattern: a reverse proxy routes specific API paths to the new service while the monolith continues handling everything else. The first extraction is deliberately conservative — we choose a service with clear boundaries, low coupling to other modules, and high business value from independent deployment. The monolith and the new service run in parallel, with automated comparison tests that verify identical behavior before we cut over traffic. This first extraction establishes the patterns, tooling, and team muscle memory for all subsequent extractions.
4. **Iterative Service Extraction (Ongoing, 3-6 Weeks Per Service)** — Subsequent services are extracted following the same Strangler Fig pattern, with each extraction building on the infrastructure and patterns established by the previous ones. We prioritize extractions based on business value: services that need independent scaling, services owned by teams that need faster release cycles, and services with the highest failure blast radius. A typical enterprise decomposition extracts 5-8 services over 6-12 months, with the monolith shrinking incrementally until it either disappears or stabilizes as a manageable core that does not justify further decomposition.
5. **Production Hardening & Operational Handoff (2-4 Weeks)** — Once the target services are extracted and running in production, we harden the system: chaos engineering tests (controlled failure injection to verify resilience), load testing at 3-5x expected peak volume, runbook documentation for every service, on-call playbook creation, and team training on Kubernetes operations, distributed tracing, and incident response. We conduct a formal operational handoff where your team demonstrates they can deploy, monitor, debug, and scale the system independently. Ongoing support agreements cover architecture consulting, capacity planning, and emergency response.

---

## Frequently Asked Questions

### When should I migrate from monolith to microservices?

Migrate when the organizational cost of your monolith measurably exceeds the operational cost of distributed systems. There are five concrete signals. First, deployment friction: if deploying a change to one module requires regression testing the entire application, and your release cycle has stretched to 2+ weeks because of coordination overhead, independent deployability is a genuine need. Second, team collision: if three or more teams are committing to the same codebase and spending significant time resolving merge conflicts, waiting for shared CI pipelines, and coordinating release windows, team autonomy through service ownership will accelerate everyone. Third, scaling mismatch: if one part of your application needs 10x the compute resources of the rest — an order processing engine handling holiday spikes while user management sits idle — independent scaling avoids paying for unused capacity. Fourth, failure blast radius: if a bug or resource exhaustion in one module crashes the entire application, fault isolation through service boundaries prevents a reporting query from taking down your checkout flow. Fifth, technology constraints: if a module would benefit from a different language, database, or framework than what the monolith uses, service extraction enables polyglot architecture. If none of these five signals are present, stay with your monolith. A well-organized modular monolith is simpler to operate, easier to debug, and cheaper to maintain than a microservices architecture that does not solve a real problem.

### How much does microservices migration cost?

Costs vary based on monolith size, number of target services, infrastructure requirements, and team readiness. For a typical mid-size enterprise monolith (200,000-500,000 lines of code, 150-300 database tables, 3-5 target services), expect the following ranges. Architecture assessment and decomposition planning runs $15,000-$30,000 over 2-3 weeks — this is the phase where we determine service boundaries, assess coupling, and build the extraction roadmap. Infrastructure foundation — Kubernetes cluster, CI/CD pipelines, container registry, observability stack — costs $30,000-$60,000 and takes 2-4 weeks, though this investment is amortized across every service extraction. Per-service extraction runs $25,000-$75,000 depending on the service's complexity, database coupling, and the number of integration points with the remaining monolith. A straightforward service with clean boundaries and its own database tables might cost $25,000-$35,000. A deeply coupled service that requires Saga patterns, event-driven data synchronization, and significant API contract work costs $50,000-$75,000. A full migration extracting 5-8 services from a mid-size monolith typically totals $200,000-$500,000 over 6-12 months. Ongoing Kubernetes operations, monitoring, and maintenance add $3,000-$8,000 per month depending on cluster size and service count. These numbers assume your team will own operations after handoff — if you need FreedomDev to manage the infrastructure long-term, add $5,000-$15,000 per month for managed services.

### What are the downsides of microservices?

Microservices introduce real, non-trivial costs that many consultancies downplay. Operational complexity is the biggest one: instead of monitoring one application, you are monitoring 5-20 services, each with its own deployment pipeline, its own logs, its own failure modes, and its own scaling behavior. Kubernetes alone requires dedicated expertise — misconfigured resource limits, networking policies, or autoscaling rules cause production outages that are harder to diagnose than monolith failures. Distributed systems debugging is fundamentally more difficult. A request that previously executed in a single process now traverses 3-7 services, any of which can fail, timeout, or return unexpected data. Without proper distributed tracing (Jaeger, Zipkin) and correlation IDs, debugging production issues becomes guesswork. Latency increases because every inter-service call adds network overhead — a monolith function call that takes microseconds becomes a REST or gRPC call that takes milliseconds. At 5-7 hops per request, that overhead accumulates. Data consistency becomes eventually consistent instead of immediately consistent. You lose ACID transactions across service boundaries and replace them with Saga patterns that are more complex to implement, test, and debug. Testing complexity multiplies: integration tests require spinning up multiple services, contract testing between services must be maintained, and end-to-end tests are slower and more brittle. Finally, cost increases in the short term — infrastructure costs for Kubernetes, service mesh, monitoring tools, and the engineering investment in migration itself. The honest answer is that microservices are the right architecture for a specific set of problems, and the wrong architecture for everything else. We turn away roughly 30% of prospective microservices clients because a modular monolith or containerized monolith would serve them better.

### How do microservices communicate with each other?

There are two fundamental communication patterns, and choosing the right one per interaction is critical to system reliability. Synchronous communication — REST over HTTP or gRPC over HTTP/2 — is used when one service needs an immediate response from another. A checkout service calls the inventory service to verify stock availability before confirming an order. REST is the default for simplicity and broad tooling support; gRPC is preferred for internal service-to-service calls where you need strong typing via Protocol Buffers, bidirectional streaming, or lower latency (gRPC is typically 2-10x faster than REST for the same payload due to binary serialization and HTTP/2 multiplexing). The risk with synchronous communication is cascading failure: if the inventory service is down, the checkout service blocks. Circuit breakers (implemented via Istio service mesh or libraries like resilience4j) detect downstream failures and fail fast instead of hanging, returning a degraded response or cached data. Asynchronous communication — message queues (RabbitMQ) or event streaming (Apache Kafka) — is used when services need to react to events without blocking the caller. When an order is placed, the order service publishes an 'OrderPlaced' event to Kafka. The inventory service, shipping service, notification service, and analytics service each consume that event independently and process it on their own timeline. If one consumer is down, the message waits in the queue and is processed when the consumer recovers — no data loss, no blocking. Kafka provides ordered, durable event streams that can be replayed from any point in time, making it ideal for event sourcing architectures. RabbitMQ provides traditional message queuing with flexible routing, dead letter exchanges for failed messages, and lower operational overhead than Kafka for moderate volumes. Most microservices architectures use both patterns: synchronous for queries and commands that need immediate responses, asynchronous for events and notifications that should not block the caller.

### Do I need Kubernetes for microservices?

No, but you will almost certainly want it once you have more than 3-4 services in production. Kubernetes solves a specific set of problems that become painful at scale: service discovery (how does Service A find Service B when instances are created and destroyed dynamically), load balancing (distributing traffic across multiple instances of a service), rolling deployments (updating a service without downtime by replacing instances one at a time), self-healing (automatically restarting crashed containers), horizontal scaling (adding more instances under load and removing them when traffic drops), and resource management (preventing one service from consuming all available CPU or memory). Without Kubernetes, you manage all of this manually: Docker Compose for local development, custom scripts for deployment, Consul or etcd for service discovery, nginx or HAProxy for load balancing, and a collection of bash scripts and cron jobs for health checking and restarts. This works for 2-3 services but becomes untenable at 5+ services because the operational surface area grows quadratically. Alternatives exist. AWS ECS with Fargate provides container orchestration without managing Kubernetes itself — lower operational overhead but less flexibility and portability. Docker Swarm is simpler than Kubernetes but has a smaller ecosystem and fewer features. For very small microservices deployments (2-3 services, low traffic), running Docker containers on a single VM with Docker Compose and a reverse proxy is pragmatic and significantly cheaper than a Kubernetes cluster. FreedomDev recommends Kubernetes when you have 4+ services, need autoscaling, require rolling deployments with zero downtime, or plan to run across multiple cloud providers. For smaller deployments, we often start with ECS Fargate or Docker Compose and migrate to Kubernetes when the service count and operational requirements justify the complexity. The worst outcome is adopting Kubernetes prematurely and spending more time managing the platform than building your product.

---

## Microservices Migration Results: What Changes After Decomposition

- **4-6 weeks → daily**: Deployment frequency after monolith decomposition
- **95%**: Reduction in deployment-related downtime with rolling updates
- **70-80%**: Reduction in mean time to recovery (MTTR) with fault isolation
- **3-5x**: Improvement in engineering velocity measured by cycle time
- **Independent**: Service scaling: each service scales based on its own demand
- **< 2 min**: Average time from commit to production deploy per service

---

**Canonical URL**: https://freedomdev.com/solutions/microservices-architecture

_Last updated: 2026-05-14_