Essential Microservices Design Patterns
As I progress in my journey of learning about distributed systems, I have explored key microservices design patterns. This blog entry aims to highlight the essential concepts behind some of the most widely used patterns.
1. Strangler Fig Pattern

This pattern helps you incrementally replace legacy monolithic systems with microservices. A facade interface routes traffic to either the old system or the new microservice, allowing you to gradually phase out legacy components.
- Pro: Enables a smooth, low-risk migration by incrementally replacing functionalities.
- Con: Running two systems in parallel increases resource costs and complicates data consistency.
2. Backend for Frontend (BFF)

BFF involves creating dedicated API layers for different client types (web, mobile, TV, etc.). Each backend is tailored to the specific needs of its consumer, optimizing performance, error handling, and user experience.
- Pro: Optimizes and personalizes interactions for each client type.
- Con: Can lead to code duplication, increased maintenance overhead, and potential overfetching of data.
3. Circuit Breaker

The circuit breaker pattern improves resilience by monitoring remote calls. If a service call fails repeatedly, the circuit breaker trips, preventing further calls until the service recovers, thereby avoiding cascading failures.
It operates in three primary states:
States
- Closed: In the closed state, all requests pass through to the remote service. The circuit breaker continuously monitors the success and failure rates. If the failure threshold is exceeded, it transitions to the open state.
- Open: When in the open state, the circuit breaker immediately fails incoming requests without calling the service. This helps to quickly isolate a failing service and prevent further strain, effectively "tripping" the circuit breaker.
- Half-Open: After a predefined timeout period, the circuit breaker moves to the half-open state. Here, a limited number of test requests are allowed to pass through. If these requests succeed, the breaker assumes the service is healthy again and transitions back to the closed state; if they fail, it reverts to the open state.
Pros
- Enhances system stability by isolating failing services and preventing cascading failures.
- Improves overall system resilience by quickly responding to and containing faults.
Cons
- Adds extra complexity to service communication and state management.
- Can introduce slight latency overhead due to the constant monitoring and state transitions.
4. Service Discovery Pattern

This pattern enables microservices to automatically detect and communicate with each other. Services register with a central registry, and consumers query this registry to locate the required service.
- Pro: Promotes dynamic scaling and decouples service configurations.
- Con: Introduces additional complexity and dependency on the registry, which can become a single point of failure if not managed properly.
5. Retry Pattern
The Retry pattern automatically retries failed operations, addressing transient errors without manual intervention.
- Pro: Simple and effective for handling temporary failures.
- Con: If misconfigured, it may exacerbate load issues and lead to cascading failures.
6. Saga Pattern

The Saga pattern is a strategy for managing distributed transactions by breaking a long-lived business process into a series of smaller, independent local transactions. Each microservice executes its own transaction and then publishes an event to trigger the next step. If any step fails, compensating transactions are executed to undo the work done by preceding steps, ensuring the system eventually reaches a consistent state.
How It Works
- Local Transactions: Each microservice performs its own transaction independently, updating its local database.
- Event Coordination: After a successful transaction, an event is published to initiate the next step in the process.
- Compensating Actions: If a transaction fails, previously completed steps execute compensating transactions to rollback or adjust changes, maintaining overall consistency.
- Decentralized Control: The saga pattern eliminates the need for a centralized transaction manager by relying on a chain of coordinated events.
Pros
- Eventual Consistency: Achieves consistency across services without a monolithic, distributed transaction system.
- Service Autonomy: Each microservice manages its own data and transactions, enhancing scalability and resilience, and flexibility in how each service wants to maintain its data. For example, some services might be better suited for noSQL while others for SQL.
- Fault Isolation: A failure in one service does not immediately compromise the entire process; compensating transactions help mitigate issues.
Cons
- Complex Coordination: Designing, implementing, and testing compensating transactions across multiple services can be complex.
- Temporary Inconsistency: The system might experience brief periods of inconsistency until all steps (or compensations) are completed.
- Debugging Challenges: Tracing issues across distributed services and event chains can be difficult, requiring robust monitoring and logging.
7. Event-Driven Pattern

The Event-Driven pattern uses events to communicate between services, allowing for decoupled and asynchronous processing.
- Pro: Enhances scalability and decoupling of services.
- Con: Can be challenging to debug and maintain due to eventual consistency and asynchronous flows.
8. CQRS (Command Query Responsibility Segregation)

CQRS is a design pattern that separates the responsibilities of reading data (queries) and modifying data (commands) into two distinct models. This separation allows each side to be optimized independently, addressing specific performance, scalability, and complexity needs.
How It Works
- Command Model: Handles all operations that change state (inserts, updates, deletions). Commands encapsulate the intent to modify data and typically include validation and business logic.
- Query Model: Dedicated exclusively to reading data. This model is optimized for fast and efficient data retrieval, often structured differently than the write model to better serve various querying needs.
- Data Synchronization: Changes made via the command model are propagated to the query model. This can occur synchronously or asynchronously (often using events), meaning that the system might experience temporary inconsistencies until the two models are fully aligned.
- Independent Scaling: Since read and write workloads are separated, you can scale each model independently. For example, if the system has a high read-to-write ratio, the query side can be scaled out more aggressively without affecting write operations.
Pros
- Performance Optimization: Both the command and query sides can be fine-tuned to meet their specific performance requirements.
- Scalability: Independent scaling allows the system to efficiently handle heavy read or write loads without one impacting the other.
- Flexibility: Different storage technologies or database schemas can be used for reads and writes, enabling a more tailored approach to data management.
- Enhanced Maintainability: By separating concerns, the system’s overall architecture can become more modular and easier to evolve over time.
Cons
- Increased Complexity: Maintaining two separate models and ensuring proper synchronization adds complexity to the system design and implementation.
- Eventual Consistency: The query model might lag behind the command model, resulting in temporary inconsistencies that need to be managed and communicated to users.
- Development Overhead: The need for additional logic to handle data synchronization and model separation can increase development and testing efforts.