Mikroservis mimarisi, modern uygulamaların ölçeklenebilirlik ve bakım kolaylığı sağlamak için tercih ettiği bir yaklaşımdır. Bu rehberde Node.js, Express.js, Docker ve Kubernetes kullanarak profesyonel bir mikroservis mimarisi nasıl kuracağınızı öğreneceksiniz.
Mikroservis Mimarisi Nedir?
Mikroservis mimarisi, büyük uygulamaları küçük, bağımsız hizmetlere bölen bir yaklaşımdır. Her mikroservis:
- Kendi veritabanına sahiptir
- Bağımsız olarak deploy edilebilir
- Belirli bir iş fonksiyonunu yerine getirir
- API üzerinden diğer servislerle iletişim kurar
- Farklı teknolojilerle geliştirilebilir
Proje Yapısı
Mikroservis projemiz şu şekilde organize edilecek:
microservices-app/
├── api-gateway/
│ ├── src/
│ ├── package.json
│ └── Dockerfile
├── user-service/
│ ├── src/
│ ├── package.json
│ └── Dockerfile
├── product-service/
│ ├── src/
│ ├── package.json
│ └── Dockerfile
├── order-service/
│ ├── src/
│ ├── package.json
│ └── Dockerfile
├── docker-compose.yml
└── k8s/
API Gateway Kurulumu
API Gateway, tüm istekleri yönlendiren merkezi nokta olacak:
// api-gateway/src/index.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');
const app = express();
// Güvenlik middleware'leri
app.use(helmet());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 dakika
max: 100 // IP başına maksimum 100 istek
});
app.use(limiter);
// Mikroservislere proxy
app.use('/api/users', createProxyMiddleware({
target: 'http://user-service:3001',
changeOrigin: true,
pathRewrite: {
'^/api/users': '/users'
}
}));
app.use('/api/products', createProxyMiddleware({
target: 'http://product-service:3002',
changeOrigin: true,
pathRewrite: {
'^/api/products': '/products'
}
}));
app.use('/api/orders', createProxyMiddleware({
target: 'http://order-service:3003',
changeOrigin: true,
pathRewrite: {
'^/api/orders': '/orders'
}
}));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`API Gateway running on port ${PORT}`);
});
User Service
Kullanıcı yönetimi için ayrı bir mikroservis:
// user-service/src/index.js
const express = require('express');
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());
// MongoDB bağlantısı
mongoose.connect(process.env.MONGODB_URI || 'mongodb://user-db:27017/users');
// User schema
const userSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
name: { type: String, required: true },
createdAt: { type: Date, default: Date.now }
});
const User = mongoose.model('User', userSchema);
// Kullanıcı kaydı
app.post('/users/register', async (req, res) => {
try {
const { email, password, name } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
const user = new User({
email,
password: hashedPassword,
name
});
await user.save();
const token = jwt.sign(
{ userId: user._id },
process.env.JWT_SECRET || 'secret',
{ expiresIn: '7d' }
);
res.status(201).json({
message: 'User created successfully',
token,
user: { id: user._id, email, name }
});
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// Kullanıcı girişi
app.post('/users/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign(
{ userId: user._id },
process.env.JWT_SECRET || 'secret',
{ expiresIn: '7d' }
);
res.json({
token,
user: { id: user._id, email: user.email, name: user.name }
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`User Service running on port ${PORT}`);
});
Docker ile Containerization
Her mikroservis için Dockerfile oluşturalım:
# user-service/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY src/ ./src/
EXPOSE 3001
CMD ["node", "src/index.js"]
Docker Compose
Tüm servisleri orchestrate etmek için:
# docker-compose.yml
version: '3.8'
services:
api-gateway:
build: ./api-gateway
ports:
- "3000:3000"
environment:
- NODE_ENV=production
depends_on:
- user-service
- product-service
- order-service
user-service:
build: ./user-service
environment:
- MONGODB_URI=mongodb://user-db:27017/users
- JWT_SECRET=your-secret-key
depends_on:
- user-db
user-db:
image: mongo:6
volumes:
- user_data:/data/db
product-service:
build: ./product-service
environment:
- MONGODB_URI=mongodb://product-db:27017/products
depends_on:
- product-db
product-db:
image: mongo:6
volumes:
- product_data:/data/db
order-service:
build: ./order-service
environment:
- MONGODB_URI=mongodb://order-db:27017/orders
- USER_SERVICE_URL=http://user-service:3001
- PRODUCT_SERVICE_URL=http://product-service:3002
depends_on:
- order-db
order-db:
image: mongo:6
volumes:
- order_data:/data/db
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
user_data:
product_data:
order_data:
redis_data:
Service Discovery ve Load Balancing
Service discovery için Consul ve load balancing için NGINX kullanabiliriz:
// service-discovery/consul.js
const consul = require('consul')();
class ServiceRegistry {
async registerService(name, port, host = 'localhost') {
const serviceId = `${name}-${port}`;
await consul.agent.service.register({
id: serviceId,
name: name,
address: host,
port: port,
check: {
http: `http://${host}:${port}/health`,
interval: '10s',
timeout: '3s'
}
});
console.log(`Service ${name} registered with Consul`);
}
async discoverService(serviceName) {
const services = await consul.health.service(serviceName);
return services[0]?.Service || null;
}
}
module.exports = ServiceRegistry;
Kubernetes Deployment
Production ortamı için Kubernetes manifesti:
# k8s/user-service-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: your-registry/user-service:latest
ports:
- containerPort: 3001
env:
- name: MONGODB_URI
value: "mongodb://user-db:27017/users"
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: app-secrets
key: jwt-secret
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 3001
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3001
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- port: 3001
targetPort: 3001
type: ClusterIP
Monitoring ve Logging
Mikroservislerde monitoring kritik önem taşır:
// monitoring/logger.js
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: {
service: process.env.SERVICE_NAME || 'microservice'
},
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console({
format: winston.format.simple()
})
]
});
module.exports = logger;
Circuit Breaker Pattern
Servisler arası çağrılarda hata toleransı için:
const CircuitBreaker = require('opossum');
const options = {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000
};
const breaker = new CircuitBreaker(callExternalService, options);
breaker.on('open', () => console.log('Circuit breaker opened'));
breaker.on('halfOpen', () => console.log('Circuit breaker half-open'));
async function callUserService(userId) {
try {
return await breaker.fire(userId);
} catch (error) {
// Fallback mechanism
return { error: 'Service temporarily unavailable' };
}
}
Best Practices
- Database per Service: Her mikroservis kendi veritabanını kullanmalı
- API Versioning: Geriye dönük uyumluluk için versiyon kullanın
- Health Checks: Her servis health endpoint'i sunmalı
- Distributed Tracing: Jaeger veya Zipkin kullanın
- Security: JWT token'ları ve HTTPS kullanın
- Caching: Redis ile cache layer ekleyin
Testing Stratejisi
// tests/user-service.test.js
const request = require('supertest');
const app = require('../src/index');
describe('User Service', () => {
test('should create a new user', async () => {
const userData = {
email: '[email protected]',
password: 'password123',
name: 'Test User'
};
const response = await request(app)
.post('/users/register')
.send(userData)
.expect(201);
expect(response.body.user.email).toBe(userData.email);
expect(response.body.token).toBeDefined();
});
test('should login user', async () => {
const loginData = {
email: '[email protected]',
password: 'password123'
};
const response = await request(app)
.post('/users/login')
.send(loginData)
.expect(200);
expect(response.body.token).toBeDefined();
});
});
Sonuç
Mikroservis mimarisi, ölçeklenebilir ve bakımı kolay uygulamalar geliştirmek için güçlü bir yaklaşımdır. Node.js, Docker ve Kubernetes kombinasyonu ile modern, production-ready mikroservisler geliştirebilirsiniz.
Web Studio TR'de sunduğumuz Backend Geliştirme kurslarında mikroservis mimarisi, Docker, Kubernetes ve daha birçok konuyu uygulamalı olarak öğrenebilirsiniz.