10 Must-Know Microservices Patterns with Sample Code for Scalable Systems

Microservices architecture has become a popular approach for building scalable and maintainable applications. It involves breaking down a monolithic application into smaller, loosely coupled services that can be developed, deployed, and scaled independently. In this blog, we explore some common microservices patterns that can help you design robust and efficient systems.
1. API Gateway Pattern
The API Gateway Pattern acts as a single entry point for all client requests. Instead of directly interacting with individual microservices, clients send their requests to the API Gateway, which routes them to the appropriate microservice. This pattern simplifies client-side interactions and enables functionalities like request aggregation, authentication, and rate limiting.
Sample Code:
const express = require('express'); const proxy = require('express-http-proxy'); const app = express(); // Proxy requests to the appropriate microservice app.use('/service1', proxy('http://service1.local')); app.use('/service2', proxy('http://service2.local')); app.listen(3000, () => console.log('API Gateway running on port 3000'));
2. Circuit Breaker Pattern
Failures are inevitable in distributed systems, and the Circuit Breaker Pattern helps manage them gracefully. When a microservice becomes unresponsive or fails, the circuit breaker trips and redirects requests to a fallback mechanism. This prevents cascading failures and ensures the system remains resilient.
Sample Code:
const CircuitBreaker = require('opossum'); const options = { timeout: 3000, // If function takes longer than 3 seconds, trigger fallback errorThresholdPercentage: 50, // Break circuit if 50% of requests fail resetTimeout: 5000 // Wait 5 seconds before trying again }; const serviceCall = async () => { // Simulate a call to a microservice }; const breaker = new CircuitBreaker(serviceCall, options); breaker.fallback(() => 'Fallback response'); breaker.fire().then(console.log).catch(console.error);
3. Service Registry Pattern
In a microservices architecture, it's essential to know where each service is located. The Service Registry Pattern provides a central directory for service discovery, allowing services to find and communicate with each other dynamically. This eliminates hardcoded dependencies and supports scaling and dynamic environments.
Sample Code:
const eureka = require('eureka-js-client').Eureka; const client = new eureka({ instance: { app: 'my-service', hostName: 'localhost', ipAddr: '127.0.0.1', port: { '$': 8080, '@enabled': true }, vipAddress: 'my-service', dataCenterInfo: { '@class': 'com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo', name: 'MyOwn' } }, eureka: { host: 'eureka-server.local', port: 8761, servicePath: '/eureka/apps/' } }); client.start();
4. Service Mesh Pattern
The Service Mesh Pattern introduces an additional layer of infrastructure to handle cross-cutting concerns such as service discovery, load balancing, and security. By abstracting these responsibilities away from individual microservices, the service mesh simplifies development and improves operational efficiency.
5. Event-Driven Architecture Pattern
Communication between microservices can be challenging, especially when dealing with asynchronous workflows. The Event-Driven Architecture Pattern enables microservices to publish and subscribe to events, creating a decoupled system. This pattern is ideal for scenarios where real-time updates and scalability are critical.
Sample Code:
const EventEmitter = require('events'); const eventBus = new EventEmitter(); // Microservice A eventBus.on('userCreated', (data) => { console.log('User created event received:', data); }); // Microservice B const user = { id: 1, name: 'John Doe' }; eventBus.emit('userCreated', user);
6. Saga Pattern
The Saga Pattern is designed to handle transactions that span multiple microservices. Instead of a single, long-running transaction, it breaks the process into smaller, atomic steps. If an error occurs, compensating actions can undo the completed steps, ensuring consistency.
Sample Code:
class Saga { static async executeSteps(steps) { for (let step of steps) { try { await step(); } catch (error) { console.log('Compensating action:', error); break; } } } } const steps = [ async () => console.log('Step 1 completed'), async () => { throw new Error('Step 2 failed'); }, async () => console.log('Step 3 completed') ]; Saga.executeSteps(steps);
7. Bulkhead Pattern
To prevent a single point of failure from affecting the entire system, the Bulkhead Pattern isolates microservices. Each service runs in its own container or process, ensuring that failures are contained and do not propagate to other services.
8. Sidecar Pattern
The Sidecar Pattern involves deploying a helper container alongside each microservice to handle cross-cutting concerns like logging, monitoring, and security. By offloading these responsibilities to the sidecar, microservices can focus on their core functionality.
9. CQRS Pattern
The Command Query Responsibility Segregation (CQRS) Pattern separates the read and write models in a system. The read model is optimized for querying data, while the write model is optimized for updates. This separation enhances performance and scalability, especially for complex systems.
Sample Code:
class WriteModel { static async update(data) { console.log('Updating data:', data); } } class ReadModel { static async query(criteria) { console.log('Querying data with criteria:', criteria); } } WriteModel.update({ id: 1, value: 'Updated value' }); ReadModel.query({ id: 1 });
10. Strangler Pattern
The Strangler Pattern is a strategy for migrating from a monolithic application to microservices. It involves gradually introducing new microservices and removing functionality from the monolith, eventually replacing it entirely. This incremental approach minimizes risk and disruption.
Microservices patterns provide proven solutions to common challenges in distributed systems. By adopting the right patterns for your architecture, you can build scalable, resilient, and maintainable applications. Whether you’re just starting with microservices or looking to optimize an existing setup, these patterns offer valuable guidance.
Which of these patterns have you implemented in your projects? Let us know in the comments below!
No responses yet