Microservices

How to decompose a system into small, autonomous services that can be deployed independently, aligned with business domains.

What problem it solves

As a monolithic application grows, problems emerge that slow the team down:

  • Risky deployments: A small change requires redeploying the entire application
  • Inefficient scaling: To scale one feature, you have to scale the whole monolith
  • Blocked teams: Multiple teams work on the same codebase and block each other
  • Homogeneous technology: The entire system must use the same language, framework, and database
  • Growing build times: Compiling and testing the whole monolith takes longer and longer
Monolith:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         Application             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚Usersβ”‚ β”‚Prodsβ”‚ β”‚  Orders   β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚  β”‚      Single database        β”‚β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  One deploy = the whole system
  One failure = the whole system

How it works

The microservices architecture decomposes the system into small, independent services, each responsible for a specific business capability. Each service:

  • Has its own database (Database per Service)
  • Is deployed independently
  • Communicates with other services through APIs or events
  • Is owned by a specific team
Microservices:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Users   β”‚  β”‚ Products β”‚  β”‚  Orders  β”‚
β”‚ Service  β”‚  β”‚ Service  β”‚  β”‚ Service  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”  β”‚  β”‚  β”Œβ”€β”€β”€β”€β”  β”‚  β”‚  β”Œβ”€β”€β”€β”€β”  β”‚
β”‚  β”‚ DB β”‚  β”‚  β”‚  β”‚ DB β”‚  β”‚  β”‚  β”‚ DB β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”˜  β”‚  β”‚  β””β”€β”€β”€β”€β”˜  β”‚  β”‚  β””β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     β”‚              β”‚              β”‚
     └──────── Event Bus β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Decomposition principles

The key lies in how to divide the system. The most effective criteria are:

CriterionDescriptionExample
By business domainEach service represents a DDD bounded contextUser Service, Order Service, Payment Service
By business capabilityEach service implements an organizational capabilityBilling, Inventory, Notifications
By subdomainAligned with the business subdomainsCore (orders), Supporting (reports), Generic (auth)

Communication between services

Microservices communicate in two main ways:

Synchronous (request-response):

Order Service ──HTTP/gRPC──► Product Service
                              "Is product X in stock?"
                              ← "Yes, 50 units"

Asynchronous (events):

Order Service ──event──► Event Bus ──► Inventory Service
                "OrderCreated"          "Reserve stock"
                                  ──► Notification Service
                                        "Send email"

Database per Service

Each microservice owns its data. No other service accesses its database directly:

βœ… Correct:
  Order Service ──API──► User Service
                         "Give me the name of user 123"

❌ Incorrect:
  Order Service ──SQL──► User DB
                         "SELECT name FROM users WHERE id=123"

This guarantees encapsulation and lets each service choose the most suitable storage technology (SQL, NoSQL, cache, etc.).

Advantages

  • Independent deployment: Each service is deployed without affecting the others
  • Granular scaling: Scale only the services that need it
  • Team autonomy: Each team owns its service end-to-end
  • Technology diversity: Each service can use the most suitable language and framework
  • Resilience: A failure in one service does not necessarily bring down the whole system
  • Time to market: Small teams can deliver faster

Trade-offs / Disadvantages

  • Distributed complexity: Debugging, testing, and tracing are significantly harder
  • Eventual consistency: Distributed transactions are complex (they require Sagas, Outbox)
  • Network latency: Communication between services adds latency
  • Operational overhead: More services = more deployments, more monitoring, more infrastructure
  • Data duplication: Each service may need copies of other services’ data
  • Organizational complexity: Requires mature teams with a DevOps culture

When to use

  • Large systems with multiple development teams
  • When different parts of the system have very different scaling requirements
  • When you need to deploy features independently and frequently
  • Organizations with autonomous teams aligned with business domains
  • Systems where resilience and availability are critical

When to avoid

  • Small teams (fewer than 8-10 developers) where the overhead is not justified
  • Systems with a simple domain that does not require decomposition
  • When you don’t have mature CI/CD, monitoring, and orchestration infrastructure
  • Prototypes or MVPs where development speed is the priority
  • When you can’t define clear bounded contexts (a sign that the domain is not well understood)

Common technologies and implementations

CategoryOptions
Synchronous communicationREST, gRPC, GraphQL
Asynchronous communicationKafka, RabbitMQ, Amazon SQS, NATS
OrchestrationKubernetes, Docker Swarm, Amazon ECS
Service DiscoveryConsul, Eureka, DNS-based (Kubernetes)
ObservabilityJaeger, Zipkin, OpenTelemetry, Prometheus
API GatewayKong, Traefik, AWS API Gateway

Relationship with other patterns

  • DDD (Domain-Driven Design): Provides the criteria for defining the boundaries of each service
  • API Gateway: A single entry point for external clients
  • Saga Pattern: Handles transactions that span multiple services
  • Event-Driven Architecture: Asynchronous communication pattern between services
  • Circuit Breaker: Protects services from cascading failures

Next steps

Microservices are a powerful but demanding architectural style. To understand how to handle asynchronous communication between them, explore Event-Driven Architecture. To protect communication with external systems, review the Anti-Corruption Layer.