The microservices architecture consists of building an application as a set of small, autonomous, and independently deployable services, each responsible for a specific business capability and communicating through lightweight mechanisms such as HTTP APIs or messaging. It is one of the most popular architectural styles of the last decade, but also one of the most misunderstood: it brings enormous benefits when applied with judgment and a considerable cost in complexity when adopted because it is trendy.

In this lesson we will define its characteristics, compare it with the monolith, analyze when it is suitable (and when it is not), and see a concrete example of decomposing an insurance application.

Contents

  1. Definition and characteristics
  2. Microservices versus monolith
  3. The modular monolith as an alternative
  4. When to USE microservices
  5. When NOT to use microservices
  6. Decomposition example
  7. Common mistakes and tips
  8. Exercises
  9. Conclusion

  1. Definition and characteristics

A microservice is a service that ideally meets these characteristics:

  • Single business responsibility: it manages a specific capability (policies, claims, billing).
  • Independent deployment: it can be deployed without coordinating with the rest.
  • Its own database: each service owns its data; no one accesses its database directly.
  • Team autonomy: one team develops, tests, and operates it from end to end.
  • Communication over the network: via API or messages, never by sharing memory.
  • Fault tolerance: designed to withstand its dependencies failing.
graph TB
    subgraph "Insurance Application"
        P[Policies Service]
        S[Claims Service]
        C[Customers Service]
        F[Billing Service]
    end
    P --> DBP[(Policies DB)]
    S --> DBS[(Claims DB)]
    C --> DBC[(Customers DB)]
    F --> DBF[(Billing DB)]

Notice that each service has its own database. This is one of the most important principles: a service never reads from or writes to another's database. If it needs data belonging to another, it requests it via API or reacts to events.

  1. Microservices versus monolith

A monolith is an application deployed as a single unit. It is not inherently bad; in fact, it is usually the right starting point. The honest comparison is:

Aspect Monolith Microservices
Deployment One unit Many independent units
Scaling All or nothing Per service, as needed
Operational complexity Low High (network, observability, orchestration)
Initial velocity High Low (a lot of upfront infrastructure)
Fault isolation Poor (one failure can bring everything down) Good (contained failures)
Transactions Simple local ACID Distributed and complex (sagas)
Technology A common stack Heterogeneous per service
Team autonomy Low (everyone touches the same thing) High
Debugging Simple (one process, one trace) Complex (distributed traces)

The key takeaway: microservices trade development complexity for operational complexity. They do not eliminate it.

  1. The modular monolith as an alternative

Before jumping to microservices, there is a very underrated intermediate option: the modular monolith. It is a single deployable, but internally organized into modules with clear boundaries and explicit communication between them.

// Well-separated modules within a single deployable.
// The claims module does NOT access the internal classes of policies;
// it only uses its public API (an interface).
package com.fiatc.claims;

public class ClaimsService {

    private final PolicyApi policyApi; // public interface of the Policies module

    public ClaimsService(PolicyApi policyApi) {
        this.policyApi = policyApi;
    }

    public void registerClaim(String policyId) {
        // We query through the public API, not internal tables
        if (!policyApi.isActive(policyId)) {
            throw new IllegalStateException("The policy is not active");
        }
        // ... registration logic
    }
}

This approach gives you clean boundaries without the cost of the network. If later a module needs to scale or be deployed separately, it is already isolated and extracting it into a microservice is much easier. It is the recommended strategy to start with.

  1. When to USE microservices

Microservices make sense when several of these conditions are met:

  • The organization is large: many teams stepping on each other while working on the same code.
  • Disparate scaling needs: the billing service needs 20 instances and the configuration one needs only 1.
  • Different rates of change: some parts change daily and others almost never.
  • Availability requirements per domain: isolating a failure in claims so it doesn't bring down policy sales.
  • DevOps maturity: there is CI/CD automation, containers, observability, and orchestration.

The famous "Conway's law" sums it up: the architecture of the system tends to reflect the communication structure of the organization. If you have autonomous teams, microservices fit.

  1. When NOT to use microservices

Avoid microservices if:

  • The team is small: the operational overhead is not worth it.
  • The domain is not clear: if you still don't understand the boundaries well, you will draw them wrong and pay a very high cost to correct them.
  • There is no operational maturity: without observability or automation, debugging is a nightmare.
  • You seek extreme transaction performance: distributed transactions are slow and complex.

A common antipattern is the distributed monolith: microservices so coupled to each other that they must be deployed together. It combines the worst of both worlds: the complexity of the network and the rigidity of the monolith.

Symptom Diagnosis
Deploying one service requires deploying others Distributed monolith
A schema change affects several services Poorly defined boundaries
Services that share a database Data coupling
Long chains of synchronous calls Temporal coupling

  1. Decomposition example

Let's start from an insurance monolith with everything in a single process: customers, policies, claims, and billing. A reasonable decomposition, by business capabilities, would be:

# Resulting service map
services:
  - name: customers
    responsibility: "Contact details and profile of the insured"
    owned_data: [customer, address]
  - name: policies
    responsibility: "Underwriting and policy lifecycle"
    owned_data: [policy, coverage]
  - name: claims
    responsibility: "Opening and handling of claims"
    owned_data: [claim, appraisal]
  - name: billing
    responsibility: "Receipts, charges, and refunds"
    owned_data: [receipt, charge]

Each service owns its tables. How does the claims service get the customer's name? It does not access the customers database; it requests it via API or maintains a local copy of the few data items it needs, updated through events:

sequenceDiagram
    participant CL as Customers Service
    participant BUS as Event bus
    participant SI as Claims Service
    CL->>BUS: CustomerUpdated event
    BUS->>SI: CustomerUpdated
    Note over SI: Updates its local copy<br/>(name, anonymized tax ID)

When a customer changes their data, the customers service publishes an event. The claims service keeps a minimal copy of what it needs and updates it upon receiving the event. This avoids direct coupling to another's database.

Common Mistakes and Tips

  • Starting directly with microservices: prefer a modular monolith until you understand the domain well.
  • Sharing a database between services: breaks autonomy and creates hidden coupling. Each service, its own database.
  • Making microservices too small (nanoservices): they generate an explosion of network calls and make it harder to reason about the system.
  • Forgetting observability: invest from day one in distributed traces, metrics, and correlated logs.
  • Synchronizing everything: long chains of synchronous calls couple availability. Consider asynchronous events.

Exercises

  1. Build a table with three advantages and three drawbacks of microservices versus the monolith, justifying each point.
  2. A startup of 4 developers wants to launch an MVP in 3 months. Would you recommend microservices? Reason your answer and propose an alternative.
  3. Given an e-commerce monolith with catalog, cart, orders, and payments modules, propose a decomposition into services indicating what data each one owns and how the orders service gets the current price of a product.

Solutions

  1. Advantages: independent deployment (less coordination), selective scaling (resource savings), fault isolation (higher availability). Drawbacks: operational complexity (network, orchestration), distributed transactions (sagas), harder debugging (distributed traces).

  2. No. With 4 people and pressure to validate the product, the operational overhead of microservices would delay the MVP. Recommendation: a modular monolith with clean boundaries, which allows extracting services later if the product grows.

  3. Catalog owns product and price; cart owns cart_line; orders owns order and order_line; payments owns transaction. The orders service gets the price by calling the catalog API at the moment of confirming the order and, very importantly, stores the applied price in its own order_line table, because the catalog price may change afterwards.

Conclusion

Microservices are small, autonomous, and independently deployable services, each owning its data. They trade development complexity for operational complexity, so they are only worthwhile when the organization, the scaling, and the technical maturity justify it. When in doubt, the modular monolith is an excellent starting point.

The hardest question that remains open is where to draw the boundaries between services. We will dedicate the next lesson, Service Decomposition and Bounded Contexts, to this, where we will apply the ideas of Domain-Driven Design to get the size and limits of each service right.

Application Architecture Course

Module 1: Fundamentals of Application Architecture

Module 2: Design Principles and Tactics

Module 3: Architectural Styles and Patterns

Module 4: Distributed Architectures and Microservices

Module 5: Event-Driven Architectures and Messaging

Module 6: Domain-Driven Design (DDD)

Module 7: Data and Persistence

Module 8: Cloud Architecture and Deployment

Module 9: Quality, Security and Observability

Module 10: Evolution, Governance and Case Studies

© Copyright 2026. All rights reserved