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.