Architecture Styles Essential

Microservices Architecture

Independently deployable services, each aligned to a bounded business capability, communicating over a network with their own data stores.

⏱ 10 min read

What it is

Microservices is an architectural style that structures an application as a collection of small, independently deployable services. Each service is owned by a small team, implements a single business capability, communicates with other services via well-defined APIs (HTTP/REST, gRPC, or messaging), and maintains its own data store. Services are independently versioned, deployed, and scaled. The "micro" in microservices refers to the scope of responsibility — each service does one thing well — not necessarily to the size of the codebase. A microservice can be tens of thousands of lines of code if it owns a rich business domain; the key characteristic is independent deployability and data ownership.

Why it exists

Microservices emerged from the pain of large-scale monolith deployment bottlenecks at companies like Netflix, Amazon, and eBay. Conway's Law states that organizations design systems mirroring their communication structure — a large team working on a single codebase naturally creates coordination overhead and slow deployments. Microservices flips this around: small, autonomous teams own small services, enabling parallel development, independent deployment cadences, and failure isolation. The architecture also enables technology heterogeneity — a machine learning service can use Python while the order service uses Java. The cost is significant operational complexity, which is why microservices are an organizational solution as much as a technical one.

When to use

  • Multiple autonomous teams need to deploy independently without blocking each other on a shared release train.
  • Different components have dramatically different scaling requirements that justify independent scaling.
  • The organization has DevOps maturity: container orchestration, CI/CD pipelines, distributed tracing, and on-call culture are all in place.
  • The domain is well understood — bounded contexts are stable and unlikely to require frequent boundary changes.
  • Technology heterogeneity is genuinely required — different services benefit from different languages, databases, or ML runtimes.

When not to use

  • Team size is small (under ~15 engineers) — the operational overhead of microservices will dominate productive feature work.
  • The domain is not yet well understood — wrong service boundaries become expensive network contracts that are hard to change.
  • The organization lacks DevOps maturity — without CI/CD automation, container platforms, and distributed observability, microservices become chaos.
  • Strong consistency across the system is a hard requirement — distributed transactions are extremely complex.

Typical architecture

Clients interact through an API Gateway. Services communicate synchronously via REST/gRPC or asynchronously via a message broker. Each service owns its own database, and a service mesh handles cross-cutting concerns like mTLS and retries.

  Client / Mobile / Web
          │
   ┌──────▼──────┐
   │  API Gateway │  (auth, rate-limit, routing)
   └──┬──┬──┬───┘
      │  │  │
┌─────▼┐ │ ┌▼──────┐
│Order │ │ │User   │   Each service:
│Svc   │ │ │Svc    │   - owns its DB
└──┬───┘ │ └───┬───┘   - deploys independently
   │   ┌─▼──┐  │       - scales independently
   │   │Pay │  │
   │   │Svc │  │
   │   └──┬─┘  │
   │      │    │
┌──▼──────▼────▼──────┐
│   Message Broker     │  (Kafka / RabbitMQ)
└──────────────────────┘
   Service Mesh (Istio/Linkerd): mTLS, retries, circuit breakers

Pros and cons

Pros

  • Independent deployability — teams ship on their own schedules.
  • Fault isolation — a crashed service does not bring down the entire system.
  • Independent scalability — scale only the services under load.
  • Technology freedom — choose the right tool for each service.
  • Smaller codebases are easier for a team to fully understand and own.

Cons

  • Massive operational overhead — container orchestration, service discovery, distributed tracing, multi-service CI/CD.
  • Distributed system fallacies apply — the network is not reliable, latency is not zero.
  • No distributed transactions — eventual consistency is the default and requires careful design.
  • Integration testing is hard — spinning up multiple services locally is complex.
  • Service boundaries are hard to change once they become network contracts.

Implementation notes

Align service boundaries to bounded contexts from Domain-Driven Design, not to technical layers. Avoid sharing databases between services — this is the single most common mistake that creates hidden coupling. Each service must be independently deployable, so invest early in a robust CI/CD pipeline per service. Implement distributed tracing (OpenTelemetry) from day one — debugging cascading failures across services without traces is nearly impossible. Use a service mesh for cross-cutting concerns (mTLS, circuit breaking, retries) rather than implementing them in every service individually. Start with an API Gateway to centralize authentication, rate limiting, and request routing.

Common failure modes

  • Shared database: Two services writing to the same database schema — the most common anti-pattern that recreates monolith coupling across a network.
  • Chatty services: A single user action triggers 10 synchronous service-to-service calls, amplifying latency and failure probability with each hop.
  • Distributed monolith: Services are separately deployed but tightly coupled through shared libraries, synchronous call chains, or shared configuration.
  • Missing observability: No distributed tracing means production bugs become impossible to debug across service boundaries.
  • Wrong service boundaries: Boundaries drawn around technical concerns (Auth, DB, Cache) rather than business capabilities lead to enormous amounts of inter-service communication for simple operations.

Decision checklist

  • Does each team own exactly one or a few services end-to-end (you build it, you run it)?
  • Is there a mature container orchestration platform (Kubernetes) and CI/CD pipeline per service?
  • Is distributed tracing (OpenTelemetry) and centralized logging in place before go-live?
  • Does each service own its own database with no direct shared access?
  • Are service boundaries aligned to stable bounded contexts, not technical layers?
  • Is an API contract testing strategy (consumer-driven contracts with Pact) defined?

Example use cases

  • Netflix: Decomposed its DVD-era monolith into hundreds of microservices to enable independent team deployments and massive horizontal scaling across AWS regions.
  • Amazon: Jeff Bezos's "two-pizza team" mandate and internal service APIs became the blueprint for AWS itself — services as independently deployable products.
  • Uber: Rider, driver, pricing, and dispatch services evolve independently and can tolerate partial failures without taking down the entire platform.

Further reading