Essential Microservice Design Patterns: Pros, Cons, and When to Use Them

This article explains the evolution of microservice architecture, defines its core characteristics, lists its advantages and disadvantages, and provides detailed guidance on when to adopt it, followed by an extensive review of key design patterns such as database per service, event sourcing, CQRS, Saga, BFF, API gateway, Strangler, circuit breaker, externalized configuration, and consumer‑driven contract testing.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
Essential Microservice Design Patterns: Pros, Cons, and When to Use Them

Microservice Architecture

Since the 1960s software engineers have struggled with system complexity. Traditional modularization (Parnas, Dijkstra, SOA) worked until about 2010, when web‑scale applications required a new approach: microservice architecture, which keeps the divide‑and‑conquer principle but implements it differently.

What Is a Microservice?

Microservice architecture splits a large system into vertically‑aligned, independently deployable processes that communicate via lightweight synchronous (REST, gRPC) or asynchronous (messaging) network calls.

Microservice architecture diagram
Microservice architecture diagram

Important Features

Application is divided into independent sub‑processes containing multiple internal modules.

Unlike modular monoliths or SOA, services are split vertically by business domain.

Boundaries are external; services talk over the network.

Each service runs in its own process and can be deployed independently.

Communication is lightweight without a heavyweight integration layer.

Advantages

Better development scale.

Faster development speed.

Supports iterative or incremental development.

Leverages modern ecosystems (cloud, containers, DevOps, serverless).

Enables horizontal and fine‑grained scaling.

Smaller codebases reduce cognitive load.

Disadvantages

Higher number of active components (services, databases, processes, containers, frameworks).

Complexity moves from code to infrastructure.

More RPC calls and network traffic.

System‑wide security management becomes harder.

Overall system design becomes more difficult.

Introduces distributed‑system complexity.

When to Use Microservices

Large‑scale web application development.

Cross‑team enterprise projects.

When long‑term benefits outweigh short‑term costs.

When the team includes architects or senior engineers capable of designing microservices.

Microservice Design Patterns

Database per Microservice

Replacing a monolith with microservices requires a decision about data storage. Keeping a single central database is an anti‑pattern; each service should own its logical data store, which may be physically shared but isolated by schema, tables, or collections.

Database per microservice diagram
Database per microservice diagram

Pros

Data fully owned by the service.

Reduced coupling between development teams.

Cons

Data sharing between services becomes more challenging.

ACID transactions across services are difficult.

Designing the split of a monolithic database is complex.

Use this pattern for large enterprise applications or when teams need full control over service data to scale development speed.

Avoid it for small applications or when a single team owns all services.

Event Sourcing

When services use exclusive databases, asynchronous event‑driven communication is preferred. Event sourcing stores every state‑changing event instead of the current state, allowing reconstruction of entity state by replaying events.

Event sourcing diagram
Event sourcing diagram

Pros

Provides atomic operations for highly scalable systems.

Automatically records change history with temporal queries.

Enables loosely‑coupled, event‑driven services.

Cons

Reading the current state requires additional storage (CQRS).

Overall system complexity increases, often needing domain‑driven design.

Handling duplicate or lost events adds complexity.

Event schema evolution is challenging.

Suitable for high‑throughput transactional systems using relational or NoSQL databases, especially in message‑driven domains such as e‑commerce.

Not suitable for low‑scale SQL transactional systems or simple synchronous microservices.

Typical technologies: EventStoreDB, Apache Kafka, AWS Kinesis, Azure Event Hub, GCP Pub/Sub, MongoDB, Cassandra, DynamoDB; frameworks: Lagom, Akka, Spring, Axon, Eventuate.

CQRS (Command‑Query Responsibility Segregation)

When event sourcing makes reading data difficult, CQRS separates the command side (writes) from the query side (reads). Simple CQRS uses separate models for read and write; advanced CQRS pairs with event sourcing and may use distinct storage for each.

Simple CQRS diagram
Simple CQRS diagram
Advanced CQRS diagram
Advanced CQRS diagram

Pros

Faster reads in event‑driven microservices.

High data availability.

Read and write systems can scale independently.

Cons

Read store is eventually consistent.

System complexity increases; misuse can jeopardize projects.

Use when read‑heavy workloads or when read/write consistency requirements differ.

Saga

Saga provides distributed transaction management for microservices with exclusive databases. It consists of a sequence of local transactions, each publishing an event that triggers the next step. Compensating transactions roll back previous work if a step fails.

Saga diagram
Saga diagram

Pros

Ensures consistency for highly scalable, loosely‑coupled, event‑driven microservices.

Works with non‑relational databases that lack 2PC.

Cons

Requires handling of instant failures and idempotency.

Debugging is hard and complexity grows with service count.

Use in high‑scalable, event‑sourced systems or when using distributed NoSQL databases.

Avoid for low‑scale relational transactional systems or when services have circular dependencies.

Technologies: Axon, Eventuate, Narayana.

Backend for Frontend (BFF)

When a single backend serves both web and mobile clients, differing UI requirements justify a dedicated backend per UI. BFF encapsulates downstream services, reduces UI‑to‑service chatter, and can improve security.

BFF diagram
BFF diagram

Pros

Separates concerns per UI, allowing optimization.

Higher security.

Reduces frequent communication between UI and downstream services.

Cons

Potential code duplication across BFFs.

Many BFFs increase maintenance effort.

Requires careful design to keep business logic out of BFF.

Use when multiple UIs have distinct API needs, when security layers are required, or with micro‑frontend architectures.

Avoid when all UIs share the same API or when core services are not behind a DMZ.

Supported by any backend framework (Node.js, Spring, Django, etc.).

API Gateway

An API gateway sits between clients and microservices, acting as a façade, routing requests, aggregating responses, and handling cross‑cutting concerns such as SSL termination, authentication, rate limiting, and logging.

API gateway diagram
API gateway diagram

Pros

Provides loose coupling between front‑end and back‑end services.

Reduces number of client‑to‑service calls.

Enables high security via SSL termination, auth, and authz.

Centralizes cross‑cutting concerns (logging, monitoring, throttling, load balancing).

Cons

Can become a single point of failure.

Additional network hop adds latency.

May become a bottleneck if not scaled.

Increases maintenance and development cost.

Use in complex microservice landscapes or large enterprises needing centralized security.

Avoid for small projects where security and central management are not priorities.

Technologies: Amazon API Gateway, Azure API Management, Apigee, Kong, WSO2 API Manager.

Strangler

The Strangler pattern incrementally replaces parts of a monolith with new microservices, routing traffic through a façade (often an API gateway) until the legacy system can be retired.

Strangler pattern diagram
Strangler pattern diagram

Pros

Safe migration from monolith to microservices.

Allows parallel development of new features and migration.

Provides better control over migration pace.

Cons

Sharing data between legacy and new services is challenging.

Facade adds latency.

End‑to‑end testing becomes harder.

Use for large backend monoliths needing incremental migration.

Avoid for small monoliths where full replacement is simpler.

Implementation typically uses an API‑gateway‑backed backend framework.

Circuit Breaker

Circuit breaker protects a service from cascading failures by monitoring recent error rates and short‑circuiting calls when a threshold is exceeded. It has three states: closed, open, and half‑open.

Circuit breaker diagram
Circuit breaker diagram

Pros

Improves fault tolerance and resilience.

Prevents cascading failures.

Cons

Requires complex exception handling.

Needs logging, monitoring, and manual reset.

Use in tightly‑coupled, synchronous microservice communication where services depend on many others.

Avoid in loosely‑coupled, event‑driven architectures or when a service has no dependencies.

Libraries: Hystrix, Resilience4j, Polly; also available via service mesh or API gateways.

Externalized Configuration

Storing configuration outside the codebase (environment variables, external files) separates build from runtime, reduces security risk, and allows changes without rebuilding.

Pros

Configuration is not part of source, minimizing security exposure.

Changes do not require rebuilding the application.

Cons

Requires a framework that supports externalized configuration.

Use for any production‑grade application.

Avoid only in proof‑of‑concept development.

Supported by virtually all modern enterprise frameworks.

Consumer‑Driven Contract Testing

Consumer teams define contracts (expected requests and responses) for provider services. Providers run these contracts as part of their automated tests, ensuring API compatibility.

Pros

Detects unexpected provider changes quickly.

Reduces surprises and increases robustness in large microservice ecosystems.

Improves team autonomy.

Cons

Additional effort to develop and integrate contract tests on the provider side.

Mismatch between contracts and real services can cause production failures.

Use in large enterprise applications with multiple teams.

Not needed for small, single‑team projects or stable services.

Tools: Pact, Postman, Spring Cloud Contract.

Summary

Microservice architecture enables scalable enterprise software development but is not a universal silver bullet; it should be adopted only when its benefits outweigh the added complexity. Core design patterns such as database‑per‑service, event sourcing, CQRS, and Saga address data consistency, while BFF and API gateways handle multi‑client communication and cross‑cutting concerns. Circuit breakers improve resilience, the Strangler pattern eases migration from monoliths, externalized configuration is essential for modern deployments, and consumer‑driven contract testing safeguards inter‑service contracts.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design Patterns
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.