Microservice Architecture Explained — Decomposition, Communication, Data, and Trade-offs
Microservice architecture splits an application into small independent services. Learn how decomposition, inter-service communication, data management, and the microservice premium work.
On this page
- How microservices decompose a system
- Bounded Context and Domain-Driven Design
- The Strangler Fig Pattern
- Granularity: finding the right size
- How services communicate
- Smart endpoints and dumb pipes
- Synchronous and asynchronous patterns
- Resiliency in distributed systems
- Data in a microservices world
- Database-per-service
- Eventual consistency
- The Saga pattern
- Common challenges
- When microservices make sense
- FAQ
- How does microservice architecture differ from service-oriented architecture (SOA)?
- What is the difference between microservices and a modular monolith?
- How do you handle data consistency across microservices?
- When should you not use microservices?
- Sources
When an application starts with five developers and one codebase, a monolithic architecture works fine. When it grows to fifty engineers, five teams, and a hundred thousand users, the monolith becomes a constraint. Microservice architecture offers an alternative: instead of one big deployable unit, you build many small ones, each owning a piece of the business, each scaling and failing independently. This guide explains the patterns that make decomposition work, how services actually communicate, what happens to your data, and when the trade-off is worth it.
How microservices decompose a system
The hardest part of microservices is not writing the services — it is deciding where the boundaries go. A bad boundary creates coupling between services that defeats the whole point; a good boundary isolates change and lets teams work independently.
Bounded Context and Domain-Driven Design
The primary tool for drawing boundaries is Domain-Driven Design's Bounded Context pattern. DDD recognizes that a large domain model cannot be unified across a whole organisation — different teams use different vocabulary, and forcing everyone onto one schema causes conflicts and slowdowns. A Bounded Context defines a clear boundary within which a single model, called a Ubiquitous Language, applies. Microservices typically map one-to-one onto a Bounded Context: the "orders" service owns the "order" model and nothing outside it can read or write that data directly.
Martin Fowler describes this as the key strategic insight of DDD: instead of one massive unified model, you build several smaller models, each coherent within its boundary, with explicit rules for how the boundaries relate to each other.
Wikipedia adds that this one-to-one mapping between Bounded Context and microservice is "typically ideal as it maintains clear boundaries, reduces coupling, and enables independent deployment and scaling."
The Strangler Fig Pattern
When you have a working monolith and want to migrate to microservices, the Strangler Fig pattern makes it gradual. Coined by Martin Fowler after the rainforest vine that grows around a host tree, it means: build new functionality as separate services, route traffic to them using an API gateway, and slowly migrate existing behaviour from the monolith until the monolith can be switched off. Microsoft Learn describes it as supporting "incremental migration from a legacy system by gradually replacing specific pieces of functionality with new services. Consumers continue to use the same interface, unaware that the migration is taking place."
Granularity: finding the right size
Microservices are fine-grained by definition, but how fine? Wikipedia warns that very fine-grained services are easier to test and deploy independently but suffer lower performance and reduced reliability due to more network hops. Coarse-grained services are more robust but harder to test in isolation. The right size depends on team structure, fault isolation needs, and scalability requirements — there is no universal formula.
How services communicate
Smart endpoints and dumb pipes
The microservice community follows the principle of smart endpoints and dumb pipes. Services own their domain logic and act as filters — they receive a request, apply logic, and produce a response. The infrastructure between them, whether an HTTP bus or a message queue, stays simple. Martin Fowler puts it simply: "Applications built from microservices aim to be as decoupled and as cohesive as possible."
Synchronous and asynchronous patterns
Wikipedia identifies the three main approaches: synchronous HTTP/REST, asynchronous messaging, and GraphQL. Synchronous calls are simple but couple services in time — if the callee is slow, the caller waits. Asynchronous messaging (RabbitMQ, Kafka, Cloudflare Queues) decouples in time: the producer sends a message and moves on; consumers pick it up when ready.
Resiliency in distributed systems
Microsoft Learn lists the three patterns every microservice developer must handle: Retry (a transient failure should be retried a few times before giving up), Circuit Breaker ("too many failed requests can cause a bottleneck, as pending requests accumulate in the queue... The Circuit Breaker pattern can prevent a service from repeatedly trying an operation that is likely to fail"), and Distributed Tracing ("a single transaction might span multiple services. That can make it hard to monitor the overall performance and health of the system").
Data in a microservices world
Database-per-service
The defining data principle of microservices is that each service owns its own database. This lets every service choose the right data store for its workload — a search service might use Elasticsearch, an orders service PostgreSQL, a recommendation service Redis. Wikipedia summarises the benefit: services "can be monitored and scaled independently" because they run in independent processes.
Eventual consistency
When each service owns its own database, a business transaction that touches three services cannot use a single local transaction. Instead, the system tolerates temporary inconsistency. Wikipedia defines eventual consistency as a model "used in distributed computing to achieve high availability. An eventually consistent system ensures that if no new updates are made to a given data item, eventually all read accesses to that item will return the last updated value."
The trade-off is expressed as BASE versus ACID. Wikipedia contrasts the two: "Eventually-consistent services are often classified as providing BASE semantics (basically-available, soft-state, eventual consistency), in contrast to traditional ACID (atomicity, consistency, isolation, durability)." Critics note that eventual consistency provides only a liveness guarantee without safety guarantees — any intermediate value can be read before convergence.
The Saga pattern
When a business operation spans several services, you replace a distributed ACID transaction with a Saga. Microsoft Learn defines it precisely: "The Saga pattern manages transactions by breaking them into a sequence of local transactions. Each local transaction: completes its work atomically within a single service; updates the service's database; initiates the next transaction via an event or message. If a local transaction fails, the saga performs a series of compensating transactions to reverse the changes."
Two approaches exist. Choreography distributes control — each service reacts to events from the previous one. Orchestration centralises control through a single orchestrator that tells each service what to do. Microsoft Learn notes that choreography works well for simple workflows ("no single point of failure; responsibilities distributed") but becomes hard to follow for complex flows and makes integration testing difficult. Orchestration suits complex workflows better but introduces a central point of failure.
Common challenges
Microservices introduce complexity that does not exist in a monolith. Wikipedia categorises the main concerns:
- Information barriers — services cannot query each other's data directly, so reports and analytics require additional data-sharing mechanisms.
- Network cost — "Inter-service calls over a network have a higher cost in terms of network latency and message processing time than in-process calls within a monolithic service process."
- Testing complexity — testing one service in isolation is straightforward, but testing interactions between services requires contracts, mocks, or a full integration environment.
- Two-phase commit is an anti-pattern — "Two-phased commits are regarded as an anti-pattern in microservices-based architectures, resulting in a tighter coupling of all the participants within the transaction." The alternative is Saga.
- Timeout antipattern — "Short timeouts may fail legitimate requests prematurely, while long timeouts can result in slow error responses."
Martin Fowler adds that the sheer number of services requires "decentralized continuous delivery and DevOps with holistic service monitoring" to operate effectively. Microservices also need a Bulkhead pattern — Microsoft Learn describes it as isolating "critical resources, such as connection pools, memory, and CPU, for each workload or service. This isolation prevents a single workload or service from consuming all resources and prevents cascading failures."
When microservices make sense
This is the most important question, and Martin Fowler's Microservice Premium gives the clearest guidance. His central thesis: "The microservices approach is all about handling a complex system, but in order to do so the approach introduces its own set of complexities." The costs are real — automated deployment, monitoring, failure handling, eventual consistency.
His primary guideline: "don't even consider microservices unless you have a system that's too complex to manage as a monolith. The majority of software systems should be built as a single monolithic application. Do pay attention to good modularity within that monolith, but don't try to separate it into separate services."
The complexity that drives teams toward microservices can come from large teams, multi-tenancy, many user interaction models, independent business-function evolution, or sheer size — a monolith that is "too big to modify and deploy." Until you hit that wall, a well-modularised monolith is faster to build and easier to operate.
FAQ
How does microservice architecture differ from service-oriented architecture (SOA)?
Microservices are a specific form of SOA that emphasises extreme decentralisation. Where traditional SOA often uses an enterprise service bus (ESB) as a smart intermediary, microservices prefer "dumb pipes" and smart endpoints. Microservices also favour independent deployment of each service and often different programming languages per service, whereas SOA tends toward more integration at the bus. The microservices movement grew partly as a reaction to heavyweight ESB-centric SOA.
What is the difference between microservices and a modular monolith?
A modular monolith is one deployable unit with enforced internal boundaries — modules or packages that separate concerns. A microservices architecture has the same separation but also runs each module as an independent process with its own database. The modular monolith is simpler to develop and deploy; the microservices architecture allows independent scaling and different technology stacks per module. If you can enforce module boundaries in a monolith, you can migrate to microservices later without rewriting the logic.
How do you handle data consistency across microservices?
The pragmatic answer is eventual consistency via the Saga pattern or event-driven updates. Instead of a single ACID transaction, you accept that services will be briefly out of sync and use compensating transactions or event subscriptions to bring them back in sync. For read-heavy scenarios, CQRS (Command Query Responsibility Segregation) separates the write model from the read model, letting read services maintain their own query-optimised data copies without blocking writes.
When should you not use microservices?
Avoid microservices for simple CRUD applications, small teams, or early-stage products where you are still discovering the domain model. The overhead of managing many services — CI/CD pipelines, service discovery, distributed tracing, separate databases — is only worth it when the scale or team complexity genuinely requires it. Fowler's Microservice Premium is the test: if your monolith is not already causing problems, the migration cost will outweigh the benefits.
Sources
- Wikipedia — Microservices (CC BY-SA 4.0)
- Martin Fowler — Microservices
- Martin Fowler — Bounded Context
- Martin Fowler — Strangler Fig Application
- Microsoft Learn — Saga Pattern
- Microsoft Learn — Microservice Design Patterns
- Wikipedia — Eventual consistency (CC BY-SA 4.0)
- Martin Fowler — Microservice Premium
- Microsoft Learn — Interservice Communication
Sources
- Wikipedia — Microservices (en.wikipedia.org/wiki/Microservices
- CC BY-SA 4.0)
- Martin Fowler — Microservices (martinfowler.com/articles/microservices.html)
- Martin Fowler — Bounded Context (martinfowler.com/bliki/BoundedContext.html)
- Martin Fowler — Strangler Fig Application (martinfowler.com/bliki/StranglerFigApplication.html)
- Microsoft Learn — Saga Pattern (learn.microsoft.com/en-us/azure/architecture/patterns/saga)
- Microsoft Learn — Microservice Design Patterns (learn.microsoft.com/en-us/azure/architecture/microservices/design/patterns)
- Wikipedia — Eventual consistency (en.wikipedia.org/wiki/Eventual_consistency
- CC BY-SA 4.0)
- Martin Fowler — Microservice Premium (martinfowler.com/bliki/MicroservicePremium.html)
- Microsoft Learn — Interservice Communication (learn.microsoft.com/en-us/azure/architecture/microservices/interservice-communication)
Share this guide
Related guides
- What is a message queue? How message queues work and when to use them
A message queue is a software component that lets services exchange data asynchronously. Learn how queues, topics, publishers and consumers work, and when they are worth the complexity.
- What is Event-Driven Architecture? How EDA works with events, CQRS, and event sourcing
Event-driven architecture (EDA) is a software design pattern where components communicate by producing and detecting events. Learn how EDA works, its topologies, event sourcing, CQRS, and when to use it.
- What is a database transaction? ACID properties explained
A database transaction bundles several operations into one all-or-nothing unit. This guide explains transactions, the ACID properties (atomicity, consistency, isolation, durability), and isolation levels.