Master essential microservices patterns to build scalable, resilient, and maintainable distributed systems. Learn about service discovery, circuit breakers, API gateways, and event-driven architectures. 🚀
Service Discovery Pattern 🔍
// Service Registry Implementation
interface ServiceInstance {
id: string;
name: string;
host: string;
port: number;
health: string;
metadata: Record<string, string>;
}
class ServiceRegistry {
private services: Map<string, ServiceInstance[]>;
constructor() {
this.services = new Map();
}
register(instance: ServiceInstance): void {
const instances = this.services.get(instance.name) || [];
instances.push(instance);
this.services.set(instance.name, instances);
}
deregister(instance: ServiceInstance): void {
const instances = this.services.get(instance.name) || [];
const index = instances.findIndex(i => i.id === instance.id);
if (index !== -1) {
instances.splice(index, 1);
this.services.set(instance.name, instances);
}
}
getInstances(serviceName: string): ServiceInstance[] {
return this.services.get(serviceName) || [];
}
getAllServices(): string[] {
return Array.from(this.services.keys());
}
}
// Service Discovery Client
class ServiceDiscoveryClient {
private registry: ServiceRegistry;
private loadBalancer: LoadBalancer;
constructor(registry: ServiceRegistry) {
this.registry = registry;
this.loadBalancer = new RoundRobinLoadBalancer();
}
async discover(serviceName: string): Promise<ServiceInstance> {
const instances = this.registry.getInstances(serviceName);
if (instances.length === 0) {
throw new Error(`No instances found for ${serviceName}`);
}
return this.loadBalancer.choose(instances);
}
}
// Load Balancer Implementation
interface LoadBalancer {
choose(instances: ServiceInstance[]): ServiceInstance;
}
class RoundRobinLoadBalancer implements LoadBalancer {
private counter = 0;
choose(instances: ServiceInstance[]): ServiceInstance {
if (instances.length === 0) {
throw new Error('No instances available');
}
const instance = instances[this.counter % instances.length];
this.counter++;
return instance;
}
}
Circuit Breaker Pattern 🔌
enum CircuitState {
CLOSED,
OPEN,
HALF_OPEN
}
interface CircuitBreakerConfig {
failureThreshold: number;
resetTimeout: number;
halfOpenTimeout: number;
}
class CircuitBreaker {
private state: CircuitState = CircuitState.CLOSED;
private failures = 0;
private lastFailureTime?: Date;
private readonly config: CircuitBreakerConfig;
constructor(config: CircuitBreakerConfig) {
this.config = config;
}
async execute<T>(
command: () => Promise<T>
): Promise<T> {
if (this.shouldRethrow()) {
throw new Error('Circuit breaker is open');
}
try {
const result = await command();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private shouldRethrow(): boolean {
if (this.state === CircuitState.OPEN) {
const now = new Date();
if (this.lastFailureTime) {
const diff = now.getTime() - this.lastFailureTime.getTime();
if (diff >= this.config.resetTimeout) {
this.state = CircuitState.HALF_OPEN;
return false;
}
}
return true;
}
return false;
}
private onSuccess(): void {
this.failures = 0;
this.state = CircuitState.CLOSED;
}
private onFailure(): void {
this.failures++;
this.lastFailureTime = new Date();
if (this.failures >= this.config.failureThreshold) {
this.state = CircuitState.OPEN;
}
}
}
// Usage Example
const circuitBreaker = new CircuitBreaker({
failureThreshold: 3,
resetTimeout: 30000, // 30 seconds
halfOpenTimeout: 5000 // 5 seconds
});
async function makeHttpRequest(): Promise<Response> {
return circuitBreaker.execute(async () => {
const response = await fetch('https://api.example.com');
if (!response.ok) {
throw new Error('Request failed');
}
return response;
});
}
API Gateway Pattern 🌐
// API Gateway Implementation
interface Route {
path: string;
method: string;
service: string;
timeout: number;
}
class ApiGateway {
private routes: Route[];
private serviceDiscovery: ServiceDiscoveryClient;
private circuitBreaker: CircuitBreaker;
constructor(
serviceDiscovery: ServiceDiscoveryClient,
circuitBreaker: CircuitBreaker
) {
this.routes = [];
this.serviceDiscovery = serviceDiscovery;
this.circuitBreaker = circuitBreaker;
}
addRoute(route: Route): void {
this.routes.push(route);
}
async handleRequest(
path: string,
method: string,
payload: any
): Promise<any> {
const route = this.findRoute(path, method);
if (!route) {
throw new Error('Route not found');
}
const service = await this.serviceDiscovery.discover(
route.service
);
const url = `http://${service.host}:${service.port}${path}`;
return this.circuitBreaker.execute(async () => {
const controller = new AbortController();
const timeout = setTimeout(
() => controller.abort(),
route.timeout
);
try {
const response = await fetch(url, {
method,
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json'
},
signal: controller.signal
});
if (!response.ok) {
throw new Error('Service request failed');
}
return response.json();
} finally {
clearTimeout(timeout);
}
});
}
private findRoute(
path: string,
method: string
): Route | undefined {
return this.routes.find(
route => route.path === path && route.method === method
);
}
}
Event-Driven Architecture 📨
// Event Bus Implementation
interface Event {
type: string;
payload: any;
metadata: {
timestamp: Date;
correlationId: string;
};
}
interface EventHandler {
handle(event: Event): Promise<void>;
}
class EventBus {
private handlers: Map<string, EventHandler[]>;
constructor() {
this.handlers = new Map();
}
subscribe(
eventType: string,
handler: EventHandler
): void {
const handlers = this.handlers.get(eventType) || [];
handlers.push(handler);
this.handlers.set(eventType, handlers);
}
async publish(event: Event): Promise<void> {
const handlers = this.handlers.get(event.type) || [];
const promises = handlers.map(handler =>
handler.handle(event).catch(error => {
console.error(
`Error handling event ${event.type}:`,
error
);
})
);
await Promise.all(promises);
}
}
// Event Sourcing Implementation
interface EventStore {
append(event: Event): Promise<void>;
getEvents(
aggregateId: string,
afterSequence?: number
): Promise<Event[]>;
}
class PostgresEventStore implements EventStore {
private pool: Pool;
constructor(connectionString: string) {
this.pool = new Pool({ connectionString });
}
async append(event: Event): Promise<void> {
await this.pool.query(
`INSERT INTO events (
aggregate_id,
sequence,
type,
payload,
metadata
) VALUES ($1, $2, $3, $4, $5)`,
[
event.payload.aggregateId,
event.payload.sequence,
event.type,
event.payload,
event.metadata
]
);
}
async getEvents(
aggregateId: string,
afterSequence = 0
): Promise<Event[]> {
const result = await this.pool.query(
`SELECT *
FROM events
WHERE aggregate_id = $1
AND sequence > $2
ORDER BY sequence`,
[aggregateId, afterSequence]
);
return result.rows.map(row => ({
type: row.type,
payload: row.payload,
metadata: row.metadata
}));
}
}
// CQRS Implementation
interface Command {
type: string;
payload: any;
}
interface CommandHandler {
handle(command: Command): Promise<void>;
}
class CommandBus {
private handlers: Map<string, CommandHandler>;
constructor() {
this.handlers = new Map();
}
register(
commandType: string,
handler: CommandHandler
): void {
this.handlers.set(commandType, handler);
}
async dispatch(command: Command): Promise<void> {
const handler = this.handlers.get(command.type);
if (!handler) {
throw new Error(
`No handler found for command ${command.type}`
);
}
await handler.handle(command);
}
}
// Saga Pattern Implementation
interface SagaStep {
execute(): Promise<void>;
compensate(): Promise<void>;
}
class Saga {
private steps: SagaStep[] = [];
private completedSteps: SagaStep[] = [];
addStep(step: SagaStep): void {
this.steps.push(step);
}
async execute(): Promise<void> {
try {
for (const step of this.steps) {
await step.execute();
this.completedSteps.push(step);
}
} catch (error) {
await this.rollback();
throw error;
}
}
private async rollback(): Promise<void> {
for (const step of this.completedSteps.reverse()) {
try {
await step.compensate();
} catch (error) {
console.error('Rollback failed:', error);
}
}
}
}
Monitoring and Observability 📊
// Distributed Tracing Implementation
interface Span {
id: string;
traceId: string;
parentId?: string;
name: string;
startTime: number;
endTime?: number;
tags: Record<string, string>;
events: {
name: string;
timestamp: number;
attributes?: Record<string, string>;
}[];
}
class Tracer {
private spans: Map<string, Span>;
constructor() {
this.spans = new Map();
}
startSpan(
name: string,
parentId?: string
): Span {
const span: Span = {
id: crypto.randomUUID(),
traceId: parentId
? this.spans.get(parentId)?.traceId
: crypto.randomUUID(),
parentId,
name,
startTime: Date.now(),
tags: {},
events: []
};
this.spans.set(span.id, span);
return span;
}
endSpan(spanId: string): void {
const span = this.spans.get(spanId);
if (span) {
span.endTime = Date.now();
}
}
addEvent(
spanId: string,
name: string,
attributes?: Record<string, string>
): void {
const span = this.spans.get(spanId);
if (span) {
span.events.push({
name,
timestamp: Date.now(),
attributes
});
}
}
setTag(
spanId: string,
key: string,
value: string
): void {
const span = this.spans.get(spanId);
if (span) {
span.tags[key] = value;
}
}
}
// Metrics Collection
interface Metric {
name: string;
value: number;
timestamp: number;
labels: Record<string, string>;
}
class MetricsCollector {
private metrics: Metric[];
constructor() {
this.metrics = [];
}
record(
name: string,
value: number,
labels: Record<string, string> = {}
): void {
this.metrics.push({
name,
value,
timestamp: Date.now(),
labels
});
}
async flush(): Promise<void> {
// Send metrics to monitoring system
await this.sendMetrics(this.metrics);
this.metrics = [];
}
private async sendMetrics(
metrics: Metric[]
): Promise<void> {
// Implementation to send metrics to monitoring system
}
}
Best Practices 📝
- Use service discovery
- Implement circuit breakers
- Use API gateways
- Implement event-driven patterns
- Use proper monitoring
- Implement proper tracing
- Use proper logging
- Implement proper security
- Use proper testing
- Follow microservices best practices
Additional Resources
Microservices architecture provides powerful patterns for building scalable and maintainable distributed systems. This guide covers essential patterns and best practices for implementing microservices.