⚡ Architecture
Microservices Architecture Design
Last updated: 2025-09-25 12:47:03
Microservices Architecture
Design and implement scalable microservices architecture with proper service communication, data management, and deployment strategies.
Service Design Principles
// User Service - Domain-driven design
class UserService {
constructor(
private userRepository: UserRepository,
private eventPublisher: EventPublisher,
private logger: Logger
) {}
async createUser(userData: CreateUserRequest): Promise {
try {
// Validate input
const validatedData = await this.validateUserData(userData);
// Check business rules
await this.checkEmailUniqueness(validatedData.email);
// Create user
const user = await this.userRepository.create(validatedData);
// Publish domain event
await this.eventPublisher.publish('user.created', {
userId: user.id,
email: user.email,
timestamp: new Date()
});
this.logger.info(`User created: ${user.id}`);
return user;
} catch (error) {
this.logger.error(`Failed to create user: ${error.message}`);
throw error;
}
}
private async validateUserData(data: CreateUserRequest): Promise {
const schema = {
email: 'required|email',
name: 'required|string|min:2|max:100',
password: 'required|string|min:8'
};
return validate(data, schema);
}
private async checkEmailUniqueness(email: string): Promise {
const existingUser = await this.userRepository.findByEmail(email);
if (existingUser) {
throw new ConflictError('Email already exists');
}
}
} API Gateway Pattern
// API Gateway with Express and middleware
const express = require('express');
const httpProxy = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const jwt = require('jsonwebtoken');
const app = express();
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP'
});
app.use(limiter);
// Authentication middleware
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ error: 'Invalid token' });
req.user = user;
next();
});
};
// Service proxies
const userServiceProxy = httpProxy({
target: process.env.USER_SERVICE_URL,
changeOrigin: true,
pathRewrite: { '^/api/users': '' },
onError: (err, req, res) => {
res.status(503).json({ error: 'User service unavailable' });
}
});
const orderServiceProxy = httpProxy({
target: process.env.ORDER_SERVICE_URL,
changeOrigin: true,
pathRewrite: { '^/api/orders': '' }
});
// Routes
app.use('/api/auth', require('./auth-routes'));
app.use('/api/users', authenticateToken, userServiceProxy);
app.use('/api/orders', authenticateToken, orderServiceProxy);
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date() });
});Event-Driven Communication
// Event Bus implementation with Redis
const Redis = require('ioredis');
class EventBus {
constructor() {
this.publisher = new Redis(process.env.REDIS_URL);
this.subscriber = new Redis(process.env.REDIS_URL);
this.handlers = new Map();
}
async publish(event, data) {
const message = {
event,
data,
timestamp: new Date(),
id: generateId()
};
await this.publisher.publish('events', JSON.stringify(message));
console.log(`Published event: ${event}`);
}
subscribe(event, handler) {
if (!this.handlers.has(event)) {
this.handlers.set(event, []);
}
this.handlers.get(event).push(handler);
}
async startListening() {
await this.subscriber.subscribe('events');
this.subscriber.on('message', async (channel, message) => {
try {
const { event, data, timestamp, id } = JSON.parse(message);
const handlers = this.handlers.get(event) || [];
for (const handler of handlers) {
try {
await handler(data, { event, timestamp, id });
} catch (error) {
console.error(`Handler error for event ${event}:`, error);
}
}
} catch (error) {
console.error('Failed to process event:', error);
}
});
}
}
// Usage in service
class OrderService {
constructor(eventBus) {
this.eventBus = eventBus;
this.setupEventHandlers();
}
setupEventHandlers() {
this.eventBus.subscribe('user.created', async (userData) => {
// Create user profile in order service
await this.createUserProfile(userData);
});
this.eventBus.subscribe('payment.processed', async (paymentData) => {
// Update order status
await this.updateOrderStatus(paymentData.orderId, 'paid');
});
}
}Service Discovery and Load Balancing
// Service registry
class ServiceRegistry {
constructor() {
this.services = new Map();
}
register(serviceName, serviceUrl, metadata = {}) {
if (!this.services.has(serviceName)) {
this.services.set(serviceName, []);
}
const serviceInfo = {
url: serviceUrl,
metadata,
registeredAt: new Date(),
lastHeartbeat: new Date()
};
this.services.get(serviceName).push(serviceInfo);
console.log(`Service registered: ${serviceName} at ${serviceUrl}`);
}
discover(serviceName) {
const services = this.services.get(serviceName) || [];
return services.filter(service =>
Date.now() - service.lastHeartbeat.getTime() < 30000 // 30 second timeout
);
}
heartbeat(serviceName, serviceUrl) {
const services = this.services.get(serviceName) || [];
const service = services.find(s => s.url === serviceUrl);
if (service) {
service.lastHeartbeat = new Date();
}
}
}
// Load balancer
class LoadBalancer {
constructor(serviceRegistry) {
this.serviceRegistry = serviceRegistry;
this.roundRobinIndex = new Map();
}
getService(serviceName, strategy = 'round-robin') {
const services = this.serviceRegistry.discover(serviceName);
if (services.length === 0) {
throw new Error(`No healthy instances of ${serviceName} found`);
}
switch (strategy) {
case 'round-robin':
return this.roundRobin(serviceName, services);
case 'random':
return services[Math.floor(Math.random() * services.length)];
case 'least-connections':
return this.leastConnections(services);
default:
return services[0];
}
}
roundRobin(serviceName, services) {
const currentIndex = this.roundRobinIndex.get(serviceName) || 0;
const nextIndex = (currentIndex + 1) % services.length;
this.roundRobinIndex.set(serviceName, nextIndex);
return services[currentIndex];
}
}