Node.js Microservices: Mimari Pattern'ler

Node.js Microservices: Mimari Pattern'ler
Microservices mimarisi, büyük ve karmaşık uygulamaları bağımsız, küçük servislere ayırarak geliştirme, dağıtım ve ölçekleme esnekliği kazandırır. Node.js, hafif yapısı ve event-driven doğası ile microservices geliştirme için ideal bir platformdur. Bu rehberde temel mimari pattern'leri, iletişim stratejilerini ve Node.js ile microservices tasarımını ele alacağız.
1. Monolith vs Microservices
Her iki yaklaşımın avantaj ve dezavantajlarını anlamak, doğru kararı vermek için önemlidir:
| Özellik | Monolith | Microservices |
|---|---|---|
| Karmaşıklık | Düşük (başlangıçta) | Yüksek |
| Deployment | Tek birim | Bağımsız servisler |
| Ölçekleme | Tüm uygulama | Servis bazlı |
| Teknoloji | Tek stack | Polyglot (karma) |
| Takım yapısı | Tek büyük takım | Küçük bağımsız takımlar |
| Veri yönetimi | Tek veritabanı | Servis başına veritabanı |
2. API Gateway Pattern
API Gateway, tüm istemci isteklerini tek bir noktadan karşılayan ve ilgili servislere yönlendiren bir katmandır.
// gateway/server.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const app = express();
// Rate limiting
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
// Auth middleware
app.use('/api', async (req, res, next) => {
const token = req.headers.authorization;
if (!token && !req.path.startsWith('/auth')) {
return res.status(401).json({ error: 'Yetkisiz' });
}
next();
});
// Servis yönlendirmeleri
app.use('/api/users', createProxyMiddleware({
target: process.env.USER_SERVICE_URL || 'http://user-service:3001',
changeOrigin: true,
pathRewrite: { '^/api/users': '' },
}));
app.use('/api/orders', createProxyMiddleware({
target: process.env.ORDER_SERVICE_URL || 'http://order-service:3002',
changeOrigin: true,
pathRewrite: { '^/api/orders': '' },
}));
app.use('/api/products', createProxyMiddleware({
target: process.env.PRODUCT_SERVICE_URL || 'http://product-service:3003',
changeOrigin: true,
pathRewrite: { '^/api/products': '' },
}));
app.listen(3000, () => console.log('API Gateway: port 3000'));
3. Service Communication Pattern'leri
Synchronous — HTTP/REST
// order-service/services/userClient.js
const axios = require('axios');
class UserClient {
constructor() {
this.baseURL = process.env.USER_SERVICE_URL || 'http://user-service:3001';
this.client = axios.create({
baseURL: this.baseURL,
timeout: 5000,
});
}
async getUser(userId) {
try {
const { data } = await this.client.get(`/users/${userId}`);
return data;
} catch (error) {
if (error.response?.status === 404) return null;
throw new Error(`User service iletişim hatası: ${error.message}`);
}
}
}
module.exports = new UserClient();
Asynchronous — Message Queue (RabbitMQ)
// shared/messageQueue.js
const amqp = require('amqplib');
class MessageQueue {
constructor() {
this.connection = null;
this.channel = null;
}
async connect() {
this.connection = await amqp.connect(process.env.RABBITMQ_URL || 'amqp://localhost');
this.channel = await this.connection.createChannel();
}
async publish(queue, message) {
await this.channel.assertQueue(queue, { durable: true });
this.channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)), {
persistent: true,
});
}
async subscribe(queue, handler) {
await this.channel.assertQueue(queue, { durable: true });
this.channel.prefetch(1);
this.channel.consume(queue, async (msg) => {
try {
const data = JSON.parse(msg.content.toString());
await handler(data);
this.channel.ack(msg);
} catch (error) {
this.channel.nack(msg, false, false); // Dead letter queue'ya gönder
}
});
}
}
module.exports = new MessageQueue();
// order-service — sipariş oluşturulduğunda event yayınla
const mq = require('../shared/messageQueue');
async function createOrder(orderData) {
const order = await Order.create(orderData);
// Event yayınla
await mq.publish('order_events', {
type: 'ORDER_CREATED',
data: order,
timestamp: new Date().toISOString(),
});
return order;
}
// notification-service — event'i dinle
const mq = require('../shared/messageQueue');
await mq.subscribe('order_events', async (event) => {
if (event.type === 'ORDER_CREATED') {
await sendOrderConfirmationEmail(event.data);
}
});
4. Circuit Breaker Pattern
Bir servis hata verdiğinde, sürekli istek göndererek sistemi daha da kötüleştirmek yerine devreyi kesin.
// shared/circuitBreaker.js
class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 5;
this.resetTimeout = options.resetTimeout || 30000;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.failureCount = 0;
this.lastFailureTime = null;
}
async execute(fn) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime >= this.resetTimeout) {
this.state = 'HALF_OPEN';
} else {
throw new Error('Circuit breaker OPEN — servis geçici olarak devre dışı');
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
}
}
}
// Kullanım
const breaker = new CircuitBreaker({ failureThreshold: 3, resetTimeout: 10000 });
async function getUserSafe(userId) {
return breaker.execute(() => userClient.getUser(userId));
}
5. Saga Pattern — Dağıtık İşlem Yönetimi
Birden fazla servisi kapsayan işlemlerde, hata durumunda telafi (compensating) işlemleri çalıştırmak için Saga pattern kullanılır.
// saga/createOrderSaga.js
class CreateOrderSaga {
constructor(orderService, paymentService, inventoryService) {
this.steps = [
{
name: 'reserve_inventory',
execute: (data) => inventoryService.reserve(data.items),
compensate: (data) => inventoryService.release(data.items),
},
{
name: 'process_payment',
execute: (data) => paymentService.charge(data.userId, data.total),
compensate: (data) => paymentService.refund(data.userId, data.total),
},
{
name: 'create_order',
execute: (data) => orderService.create(data),
compensate: (data) => orderService.cancel(data.orderId),
},
];
}
async run(orderData) {
const completedSteps = [];
for (const step of this.steps) {
try {
const result = await step.execute(orderData);
completedSteps.push(step);
orderData = { ...orderData, ...result };
} catch (error) {
// Tamamlanan adımları geri al
for (const completed of completedSteps.reverse()) {
try {
await completed.compensate(orderData);
} catch (compError) {
console.error(`Telafi hatası: ${completed.name}`, compError);
}
}
throw new Error(`Saga başarısız: ${step.name} — ${error.message}`);
}
}
return orderData;
}
}
6. Service Discovery ve Docker Compose
# docker-compose.yml
version: '3.8'
services:
gateway:
build: ./gateway
ports:
- "3000:3000"
environment:
- USER_SERVICE_URL=http://user-service:3001
- ORDER_SERVICE_URL=http://order-service:3002
depends_on:
- user-service
- order-service
user-service:
build: ./user-service
environment:
- DATABASE_URL=postgresql://user:pass@user-db:5432/users
depends_on:
- user-db
order-service:
build: ./order-service
environment:
- DATABASE_URL=postgresql://user:pass@order-db:5432/orders
- RABBITMQ_URL=amqp://rabbitmq
depends_on:
- order-db
- rabbitmq
user-db:
image: postgres:16-alpine
order-db:
image: postgres:16-alpine
rabbitmq:
image: rabbitmq:3-management-alpine
ports:
- "15672:15672"
Sonuç
Microservices mimarisi güçlü ama karmaşık bir yaklaşımdır. API Gateway ile istekleri yönetin, servisler arası iletişimde sync (HTTP) ve async (message queue) pattern'leri duruma göre seçin. Circuit Breaker ile hata yayılmasını engelleyin ve Saga pattern ile dağıtık işlemleri yönetin. Her servisin kendi veritabanına sahip olması (database per service) bağımsızlığı sağlar. Node.js'in hafif yapısı ve zengin ekosistemi, bu pattern'leri uygulamak için mükemmel bir temel sunar.