Skip to main content
Technology & EngineeringSystem Design107 lines

Microservices

Microservices architecture patterns for building independently deployable, loosely coupled services

Quick Summary13 lines
You are an expert in Microservices Architecture for designing scalable distributed systems.

## Key Points

- Synchronous: REST, gRPC, GraphQL for request/response flows.
- Asynchronous: Message brokers (Kafka, RabbitMQ, NATS) for event-driven decoupling.
- Design services around business capabilities, not technical layers; let domain boundaries guide decomposition.
- Enforce strict API contracts with schema versioning (OpenAPI, Protobuf) so services evolve independently.
- Invest in observability from day one — distributed tracing (OpenTelemetry), structured logging, and per-service health checks are non-negotiable.
- Creating "nano-services" that are too small, leading to excessive network overhead and deployment complexity without meaningful autonomy.
- Sharing databases between services, which reintroduces tight coupling and defeats the core benefit of independent deployability.
skilldb get system-design-skills/MicroservicesFull skill: 107 lines
Paste into your CLAUDE.md or agent config

Microservices — System Design

You are an expert in Microservices Architecture for designing scalable distributed systems.

Core Philosophy

Microservices are an organizational pattern as much as a technical one. The architecture mirrors team structure (Conway's Law), and the primary benefit is enabling independent teams to ship independently. If the organization does not have multiple teams that need to move at different speeds, microservices introduce distributed systems complexity without delivering their core advantage.

The defining constraint of microservices is data ownership: each service owns its data and exposes it only through its API. This is what makes independent deployment possible — without it, a shared database becomes the hidden monolith that couples every service together. Accepting this constraint means accepting eventual consistency, data duplication, and the operational cost of distributed transactions via sagas.

Start with a well-structured monolith. Extract services when you have clear, stable domain boundaries and team-level reasons to deploy independently. Premature decomposition creates distributed monoliths — systems with all the complexity of microservices and none of the autonomy, where every change requires coordinated deployments across multiple services.

Overview

Microservices architecture decomposes a system into small, autonomous services that each own a single business capability, communicate over well-defined APIs, and can be developed, deployed, and scaled independently. This contrasts with monolithic architectures where all functionality lives in a single deployable unit.

Core Concepts

Service Boundaries and Domain-Driven Design

Each microservice aligns with a bounded context from Domain-Driven Design. A service owns its data, its logic, and its API surface. Boundaries are drawn around business capabilities, not technical layers.

[Order Service] --HTTP/gRPC--> [Inventory Service]
       |                              |
       v                              v
  [Order DB]                    [Inventory DB]
       |
       +-------async event-------> [Notification Service]
                                         |
                                         v
                                   [Notification DB]

Inter-Service Communication

  • Synchronous: REST, gRPC, GraphQL for request/response flows.
  • Asynchronous: Message brokers (Kafka, RabbitMQ, NATS) for event-driven decoupling.

Service Discovery

Services register themselves with a discovery mechanism (Consul, Eureka, Kubernetes DNS) so that callers can locate them without hardcoded addresses.

Data Isolation

Each service owns its database (Database-per-Service pattern). No direct cross-service database access is allowed; data is shared through APIs or events.

Implementation Patterns

Saga Pattern for Distributed Transactions

Instead of a two-phase commit, coordinate multi-service operations via a saga — a sequence of local transactions where each step publishes an event that triggers the next. Compensating transactions handle rollback.

Strangler Fig Migration

Incrementally migrate a monolith by routing requests to new microservices while the old system still handles un-migrated features. Over time the monolith shrinks until it can be retired.

Sidecar and Service Mesh

Deploy cross-cutting concerns (TLS, retries, observability) as sidecar proxies alongside each service. A service mesh (Istio, Linkerd) manages these sidecars centrally.

API Composition

When a client needs data from multiple services, an aggregator service (or API gateway) fans out requests, merges results, and returns a unified response.

Trade-offs

FactorMicroservicesMonolith
Deployment independenceHighLow
Operational complexityHighLow
Team autonomyHighLimited
Data consistencyEventual (sagas)Strong (ACID)
LatencyNetwork hops add latencyIn-process calls
DebuggingDistributed tracing requiredStack traces sufficient

Use microservices when teams and domains are large enough to justify the operational overhead. For small teams or early-stage products, a well-structured monolith is often the better starting point.

Best Practices

  • Design services around business capabilities, not technical layers; let domain boundaries guide decomposition.
  • Enforce strict API contracts with schema versioning (OpenAPI, Protobuf) so services evolve independently.
  • Invest in observability from day one — distributed tracing (OpenTelemetry), structured logging, and per-service health checks are non-negotiable.

Common Pitfalls

  • Creating "nano-services" that are too small, leading to excessive network overhead and deployment complexity without meaningful autonomy.
  • Sharing databases between services, which reintroduces tight coupling and defeats the core benefit of independent deployability.

Anti-Patterns

  • Distributed Monolith: Splitting a monolith into services that still deploy together, share a database, or require synchronized releases. This has all the complexity of microservices with none of the independence.

  • Nano-Services: Decomposing into services so small they have no meaningful autonomy. Each "service" is a single function behind a network call, adding latency, deployment overhead, and operational burden for no architectural benefit.

  • Synchronous Chain: Service A calls B, which calls C, which calls D, all synchronously. Latency compounds, any failure in the chain cascades, and the system's availability is the product of all services' availability.

  • Shared Data Layer: Multiple services reading from and writing to the same database tables. Changes to the schema require coordinated deployments, and one service's query patterns can degrade another's performance.

  • API Versioning Neglect: Changing a service's API contract without versioning, breaking all consumers simultaneously. Strict API contracts with backward-compatible evolution are what make independent deployment possible.

Install this skill directly: skilldb add system-design-skills

Get CLI access →