← Kursa Dön
📄 Text · 40 min

Gerçek Dünya Mimarileri — Mikroservis, Monorepo, Hybrid

Docker kursumuzun son dersine geldik! Şu ana kadar Docker'ın tüm temellerini öğrendik — container'lar, image'lar, Compose, güvenlik, orchestration, CI/CD, monitoring, troubleshooting. Şimdi hepsini bir araya koyma zamanı: gerçek dünyada Docker ile uygulamalar nasıl tasarlanır ve deploy edilir? Monolith mi, mikroservis mi, yoksa ikisinin karışımı mı? Bu derste farklı mimari yaklaşımları, avantajlarını, dezavantajlarını ve ne zaman hangisini seçmen gerektiğini öğreneceğiz.

Şehir planlaması gibi düşün. Küçük bir kasabada tek bir belediye binası her işi halleder — nüfus, imar, su, elektrik hepsi aynı çatı altında (monolith). Şehir büyüdükçe ayrı müdürlükler kurulur — her biri kendi binasında, kendi ekibiyle, kendi bütçesiyle çalışır ama hepsi koordineli (mikroservis). Bazı şehirlerse karma model uygular — kritik hizmetler merkezi, geri kalanı dağıtık (hybrid).

Docker dünyasında da aynı seçimi yaparsın. Uygulamanı nasıl yapılandıracağın, kaç container olacak, nasıl haberleşecekler, nasıl deploy edilecekler — bunlar mimari kararlar. Bu ders, gerçek dünyada kullanılan Docker mimarilerini, hangi durumda hangisinin uygun olduğunu ve production'da nasıl çalıştırılacağını anlatıyor.

Mimari Seçenekler — Karşılaştırma

MimariNe Zaman?KarmaşıklıkÖlçeklemeTakım
MonolithMVP, küçük projeDüşükTamamını ölçekle1-5 kişi
Modüler MonolithOrta projeOrtaTamamını ölçekle3-10 kişi
MikroservisBüyük, karmaşık projeYüksekBağımsız ölçekle10+ kişi
Monorepo + MikroservisBüyük ekip, ortak kodYüksekBağımsız ölçekle10+ kişi
HybridGeçiş dönemleriOrta-YüksekKısmen bağımsızDeğişken

Mimari 1: Monolith — Tek Container

Tüm uygulama tek bir container'da çalışır. Basit, anlaşılır, deploy kolay.

Ne Zaman Uygun?

  • Startup / MVP — hızlı çıkmak istiyorsun

  • Küçük ekip (1-5 kişi)

  • Düşük trafik

  • Domain henüz oturmamış (sınırlar belli değil)

Mimari

Internet → Nginx (Reverse Proxy + SSL)
               │
               ▼
           App Container (API + Business Logic + Cron Jobs)
               │
          ┌────┴────┐
          ▼         ▼
      PostgreSQL   Redis

Docker Compose

# docker-compose.yml — Monolith
services:
  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - static-files:/var/www/static:ro
    depends_on:
      app:
        condition: service_healthy
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 128M

  app:
    build:
      context: .
      target: production
    expose:
      - "3000"
    environment:
      NODE_ENV: production
      DATABASE_URL: postgres://${DB_USER}:${DB_PASS}@db:5432/${DB_NAME}
      REDIS_URL: redis://redis:6379
      SESSION_SECRET: ${SESSION_SECRET}
    volumes:
      - static-files:/app/public/static
      - uploads:/app/uploads
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: "2"

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASS}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 512M

  redis:
    image: redis:7-alpine
    command: >
      redis-server
      --maxmemory 128mb
      --maxmemory-policy allkeys-lru
      --requirepass ${REDIS_PASS}
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASS}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 192M

  # Cron jobs — aynı image, farklı command
  cron:
    build:
      context: .
      target: production
    command: ["node", "dist/cron.js"]
    environment:
      NODE_ENV: production
      DATABASE_URL: postgres://${DB_USER}:${DB_PASS}@db:5432/${DB_NAME}
      REDIS_URL: redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 256M

volumes:
  pgdata:
  redis-data:
  static-files:
  uploads:

Nginx Konfigürasyonu

# nginx/nginx.conf
upstream app {
    server app:3000;
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;

    # Static files — Nginx'ten doğrudan serv et
    location /static/ {
        alias /var/www/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # API istekleri — App'e proxy et
    location / {
        proxy_pass http://app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket desteği
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    # Upload limit
    client_max_body_size 10M;
}

Mimari 2: Mikroservis — Bağımsız Servisler

Her servis bağımsız olarak geliştirilir, deploy edilir, ölçeklenir. Kendi veritabanına sahiptir (database per service).

Ne Zaman Uygun?

  • Büyük ekip (10+ kişi), birden fazla takım

  • Farklı ölçekleme ihtiyaçları (user servisi az, payment servisi çok)

  • Farklı teknoloji gereksinimleri (user: Node.js, ML: Python)

  • Bağımsız deploy gereksinimi

Mimari

Internet → API Gateway (Traefik)
               │
          ┌────┼────────────────────┐
          ▼    ▼                    ▼
     User     Order              Payment
    Service   Service            Service
       │        │                   │
       ▼        ▼                   ▼
    User DB   Order DB          Payment DB
   (Postgres) (Postgres)        (Postgres)
                │
                ▼
           Message Queue ←── Notification Service → Email/SMS
              (Redis)

Docker Compose — Mikroservis

# docker-compose.yml — Mikroservis
services:
  # === API Gateway ===
  traefik:
    image: traefik:v3.0
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      - "--api.dashboard=true"
      - "--metrics.prometheus=true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - letsencrypt:/letsencrypt
    networks:
      - traefik-public
    restart: unless-stopped

  # === User Service ===
  user-service:
    build: ./services/user
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.user.rule=PathPrefix(`/api/users`) || PathPrefix(`/api/auth`)"
      - "traefik.http.routers.user.tls.certresolver=letsencrypt"
      - "traefik.http.services.user.loadbalancer.server.port=3000"
    environment:
      DATABASE_URL: postgres://user_svc:${USER_DB_PASS}@user-db:5432/users
      REDIS_URL: redis://redis:6379
      JWT_SECRET: ${JWT_SECRET}
    networks:
      - traefik-public
      - user-net
      - shared
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 30s
      retries: 3
    restart: unless-stopped
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 256M
          cpus: "0.5"

  user-db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user_svc
      POSTGRES_PASSWORD: ${USER_DB_PASS}
      POSTGRES_DB: users
    volumes:
      - user-db-data:/var/lib/postgresql/data
    networks:
      - user-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user_svc"]
      interval: 10s
      retries: 5
    restart: unless-stopped

  # === Order Service ===
  order-service:
    build: ./services/order
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.order.rule=PathPrefix(`/api/orders`)"
      - "traefik.http.routers.order.tls.certresolver=letsencrypt"
      - "traefik.http.services.order.loadbalancer.server.port=3000"
    environment:
      DATABASE_URL: postgres://order_svc:${ORDER_DB_PASS}@order-db:5432/orders
      REDIS_URL: redis://redis:6379
      USER_SERVICE_URL: http://user-service:3000
      PAYMENT_SERVICE_URL: http://payment-service:3000
    networks:
      - traefik-public
      - order-net
      - shared
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 30s
      retries: 3
    restart: unless-stopped
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 256M

  order-db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: order_svc
      POSTGRES_PASSWORD: ${ORDER_DB_PASS}
      POSTGRES_DB: orders
    volumes:
      - order-db-data:/var/lib/postgresql/data
    networks:
      - order-net
    restart: unless-stopped

  # === Payment Service ===
  payment-service:
    build: ./services/payment
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.payment.rule=PathPrefix(`/api/payments`)"
      - "traefik.http.routers.payment.tls.certresolver=letsencrypt"
      - "traefik.http.services.payment.loadbalancer.server.port=3000"
    environment:
      DATABASE_URL: postgres://pay_svc:${PAY_DB_PASS}@payment-db:5432/payments
      STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
      REDIS_URL: redis://redis:6379
    networks:
      - traefik-public
      - payment-net
      - shared
    restart: unless-stopped
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 256M

  payment-db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: pay_svc
      POSTGRES_PASSWORD: ${PAY_DB_PASS}
      POSTGRES_DB: payments
    volumes:
      - payment-db-data:/var/lib/postgresql/data
    networks:
      - payment-net
    restart: unless-stopped

  # === Notification Service (Event-driven) ===
  notification-service:
    build: ./services/notification
    environment:
      REDIS_URL: redis://redis:6379
      SMTP_HOST: ${SMTP_HOST}
      SMTP_USER: ${SMTP_USER}
      SMTP_PASS: ${SMTP_PASS}
    networks:
      - shared
    restart: unless-stopped
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 128M

  # === Shared Infrastructure ===
  redis:
    image: redis:7-alpine
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis-data:/data
    networks:
      - shared
    restart: unless-stopped

volumes:
  letsencrypt:
  user-db-data:
  order-db-data:
  payment-db-data:
  redis-data:

networks:
  traefik-public:
    driver: bridge
  user-net:
    internal: true          # Dış erişim yok — sadece user-service ve user-db
  order-net:
    internal: true
  payment-net:
    internal: true
  shared:
    driver: bridge          # Servisler arası iletişim (Redis, inter-service calls)

Dizin Yapısı

project/
├── docker-compose.yml
├── docker-compose.override.yml      # Geliştirme override'ları
├── .env
├── services/
│   ├── user/
│   │   ├── Dockerfile
│   │   ├── package.json
│   │   ├── src/
│   │   └── tests/
│   ├── order/
│   │   ├── Dockerfile
│   │   ├── package.json
│   │   ├── src/
│   │   └── tests/
│   ├── payment/
│   │   ├── Dockerfile
│   │   └── ...
│   └── notification/
│       ├── Dockerfile
│       └── ...
├── nginx/
├── monitoring/
└── scripts/

Network İzolasyonu — Neden Önemli?

traefik-public:  Traefik + tüm servisler (dış erişim)
user-net:        user-service ↔ user-db (internal)
order-net:       order-service ↔ order-db (internal)
payment-net:     payment-service ↔ payment-db (internal)
shared:          Servisler arası iletişim (Redis)

⚠️ payment-db'ye sadece payment-service erişebilir
   user-db'ye sadece user-service erişebilir
   Güvenlik: bir servis hack'lense diğerinin DB'sine erişemez

Mimari 3: Monorepo — Tek Repo, Çoklu Servis

Tüm servisler tek bir Git repo'sunda, ama ayrı Docker image'lar olarak build ve deploy edilir.

Ne Zaman Uygun?

  • Ortak kod paylaşımı çok (shared libraries)

  • Atomic değişiklikler gerekli (birden fazla servisi etkileyen değişiklik)

  • CI/CD pipeline'ı merkezi yönetmek istiyorsun

  • Küçük-orta takım ama mikroservis mimarisi

Dizin Yapısı

monorepo/
├── packages/
│   ├── shared/                    # Ortak kütüphane
│   │   ├── src/
│   │   │   ├── auth.ts
│   │   │   ├── logger.ts
│   │   │   └── database.ts
│   │   └── package.json
│   ├── user-service/
│   │   ├── Dockerfile
│   │   ├── src/
│   │   └── package.json           # depends on @myapp/shared
│   ├── order-service/
│   │   ├── Dockerfile
│   │   ├── src/
│   │   └── package.json
│   └── payment-service/
│       ├── Dockerfile
│       ├── src/
│       └── package.json
├── docker-compose.yml
├── docker-compose.dev.yml
├── turbo.json                     # Turborepo config
├── pnpm-workspace.yaml
└── package.json

Monorepo Dockerfile

# packages/user-service/Dockerfile
FROM node:20-alpine AS base
RUN corepack enable

FROM base AS deps
WORKDIR /app
# Root package files
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml turbo.json ./
# Service + shared packages
COPY packages/user-service/package.json ./packages/user-service/
COPY packages/shared/package.json ./packages/shared/
RUN pnpm install --frozen-lockfile

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/packages/user-service/node_modules ./packages/user-service/node_modules
COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules
# Tüm kaynak kodu kopyala
COPY packages/shared ./packages/shared
COPY packages/user-service ./packages/user-service
COPY package.json pnpm-workspace.yaml turbo.json ./
# Build
RUN pnpm --filter @myapp/user-service build

FROM base AS production
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY --from=builder /app/packages/user-service/dist ./dist
COPY --from=builder /app/packages/user-service/node_modules ./node_modules
COPY --from=builder /app/packages/user-service/package.json ./
USER app
EXPOSE 3000
CMD ["node", "dist/index.js"]

Monorepo CI/CD — Akıllı Build

# .github/workflows/monorepo-ci.yml
name: Monorepo CI/CD

on:
  push:
    branches: [main]

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      user-service: ${{ steps.changes.outputs.user-service }}
      order-service: ${{ steps.changes.outputs.order-service }}
      shared: ${{ steps.changes.outputs.shared }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: changes
        with:
          filters: |
            user-service:
              - 'packages/user-service/**'
              - 'packages/shared/**'
            order-service:
              - 'packages/order-service/**'
              - 'packages/shared/**'
            shared:
              - 'packages/shared/**'

  build-user-service:
    needs: detect-changes
    if: needs.detect-changes.outputs.user-service == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/build-push-action@v5
        with:
          context: .
          file: packages/user-service/Dockerfile
          push: true
          tags: ghcr.io/myorg/user-service:${{ github.sha }}

  build-order-service:
    needs: detect-changes
    if: needs.detect-changes.outputs.order-service == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/build-push-action@v5
        with:
          context: .
          file: packages/order-service/Dockerfile
          push: true
          tags: ghcr.io/myorg/order-service:${{ github.sha }}

Mimari 4: Hybrid — Monolith + Mikroservis

Mevcut monolith'i koruyarak, yeni özellikeri ayrı servis olarak eklersin. Strangler Fig Pattern.

Ne Zaman Uygun?

  • Mevcut monolith'i tamamen yeniden yazmak riskli/pahalı

  • Yeni özellikler farklı teknoloji gerektiriyor

  • Kademeli geçiş stratejisi

Mimari

Internet → API Gateway (Nginx / Traefik)
               │
          ┌────┴──────────────────┐
          ▼                       ▼
    Legacy Monolith         New Microservices
    (PHP/Java/...)          ├── Search Service (Elasticsearch)
         │                  ├── Notification Service (Node.js)
         ▼                  └── ML/Recommendation (Python)
    Monolith DB                    │
    (MySQL)                   Service DBs

Docker Compose — Hybrid

# docker-compose.yml — Hybrid
services:
  # Mevcut monolith — containerize edilmiş
  legacy-app:
    build:
      context: ./legacy
      dockerfile: Dockerfile
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.legacy.rule=PathPrefix(`/`)"
      - "traefik.http.routers.legacy.priority=1"        # Düşük öncelik (fallback)
      - "traefik.http.services.legacy.loadbalancer.server.port=8080"
    environment:
      DB_HOST: legacy-db
      DB_NAME: myapp
    networks:
      - traefik-public
      - legacy-net
    restart: unless-stopped

  legacy-db:
    image: mysql:8.0
    volumes:
      - legacy-db-data:/var/lib/mysql
    networks:
      - legacy-net
    restart: unless-stopped

  # Yeni servisler — modern stack
  search-service:
    build: ./services/search
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.search.rule=PathPrefix(`/api/v2/search`)"
      - "traefik.http.routers.search.priority=10"       # Yüksek öncelik
      - "traefik.http.services.search.loadbalancer.server.port=3000"
    environment:
      ELASTICSEARCH_URL: http://elasticsearch:9200
      LEGACY_DB_URL: mysql://readonly:${RO_PASS}@legacy-db:3306/myapp
    networks:
      - traefik-public
      - search-net
      - legacy-net                                       # Legacy DB'ye readonly erişim
    restart: unless-stopped

  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - es-data:/usr/share/elasticsearch/data
    networks:
      - search-net
    restart: unless-stopped

  notification-service:
    build: ./services/notification
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.notification.rule=PathPrefix(`/api/v2/notifications`)"
      - "traefik.http.routers.notification.priority=10"
    networks:
      - traefik-public
      - shared
    restart: unless-stopped

  # API Gateway
  traefik:
    image: traefik:v3.0
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - traefik-public
    restart: unless-stopped

volumes:
  legacy-db-data:
  es-data:

networks:
  traefik-public:
  legacy-net:
    internal: true
  search-net:
    internal: true
  shared:

💡 İpucu: Traefik'te priority ile yeni servisler öncelikli olur. /api/v2/search yeni servise, geri kalan her şey legacy'ye gider. Böylece kademeli geçiş yaparsın.

Servisler Arası İletişim Desenleri

1. Senkron — HTTP/REST

// order-service → user-service HTTP çağrısı
const response = await fetch('http://user-service:3000/api/users/123');
const user = await response.json();

2. Asenkron — Message Queue (Redis/RabbitMQ)

// order-service → event yayınla
await redis.publish('order.created', JSON.stringify({
  orderId: 'ord-123',
  userId: 'usr-456',
  total: 99.99
}));

// notification-service → event dinle
redis.subscribe('order.created', (message) => {
  const order = JSON.parse(message);
  sendEmail(order.userId, 'Siparişiniz alındı!');
});

3. API Gateway Pattern

# Traefik routing kuralları
labels:
  # Path-based routing
  - "traefik.http.routers.user.rule=PathPrefix(`/api/users`)"
  - "traefik.http.routers.order.rule=PathPrefix(`/api/orders`)"

  # Header-based routing
  - "traefik.http.routers.v2.rule=PathPrefix(`/api`) && HeadersRegexp(`X-API-Version`, `2`)"

  # Rate limiting
  - "traefik.http.middlewares.rate-limit.ratelimit.average=100"
  - "traefik.http.middlewares.rate-limit.ratelimit.burst=50"

  # Circuit breaker
  - "traefik.http.middlewares.cb.circuitbreaker.expression=LatencyAtQuantileMS(50.0) > 100"

Anti-Pattern'ler — Yapma!

1. Dağıtık Monolith

❌ Tüm servisler birbirine bağımlı — birini deploy etmeden diğerini deploy edemezsin
❌ Senkron çağrı zincirleri: A → B → C → D (biri çökerse hepsi çöker)
❌ Paylaşımlı veritabanı (tüm servisler aynı DB'yi kullanıyor)

Çözüm:
✅ Database per service
✅ Asenkron iletişim (event-driven)
✅ Circuit breaker pattern
✅ Bağımsız deploy edilebilir servisler

2. Nano-Service

❌ Her fonksiyon ayrı servis — 50 servis, 50 container, 50 deployment
❌ Operasyonel yük çok yüksek, fayda düşük

Çözüm:
✅ Domain-driven design ile sınırları belirle
✅ Bir serviste 3-5 ilişkili endpoint olması normal
✅ Çok küçük servisler birleştir

3. Docker Compose ile Production Mikroservis

❌ 20 servis, tek sunucu, Docker Compose
   → Tek sunucu çökerse her şey çöker
   → Ölçekleme yok
   → Rolling update yok

Çözüm:
✅ 5+ servis varsa Kubernetes veya Docker Swarm kullan
✅ Docker Compose sadece geliştirme ve küçük production için

Mimari Karar Ağacı

Proje başlangıcı?
    │
    ├── Evet → Monolith + Docker Compose
    │         (hızlı başla, domain otur, sonra böl)
    │
    └── Hayır → Trafik ne kadar?
                  │
                  ├── Düşük-Orta → Modüler Monolith
                  │                (namespace ile modüller ayır)
                  │
                  └── Yüksek → Ekip büyüklüğü?
                                │
                                ├── <10 kişi → Monorepo + 3-5 servis
                                │             (Turborepo + Docker Compose/Swarm)
                                │
                                └── 10+ kişi → Mikroservis + Kubernetes
                                              (Helm/ArgoCD + managed K8s)

Best Practices

Yap:

  • Monolith ile başla — erken optimizasyon yapma, domain'i önce anla

  • Network segmentation uygula — her servisin DB'si kendi network'ünde

  • Health check her container'a ekle

  • Shared nothing prensibi — servisler veritabanı paylaşmasın

  • API versioning kullan — /api/v1/, /api/v2/

  • Circuit breaker ve retry pattern'leri uygula

  • Event-driven iletişimi tercih et (asenkron, loosely coupled)

  • Monorepo'da değişiklik tespiti yap — sadece değişen servisleri build et

Yapma:

  • Küçük proje için mikroservis mimarisi seçme — overkill

  • Tüm servisler arasında senkron HTTP çağrı zinciri kurma

  • Paylaşımlı veritabanı ile "mikroservis" yapma — dağıtık monolith olur

  • 20 servisi Docker Compose ile production'a çıkarma — orchestrator kullan

  • Her CRUD endpoint'i ayrı servis yapma — nano-service anti-pattern'i

  • Legacy sistemi bir gecede yeniden yazma — strangler fig ile kademeli geç

Özet

  • Monolith: Tek container, basit, hızlı başlangıç — MVP ve küçük projeler için ideal

  • Mikroservis: Bağımsız servisler, bağımsız deploy, bağımsız ölçekleme — büyük ekipler için

  • Monorepo: Tek repo'da çoklu servis — ortak kod paylaşımı kolaylaştırır

  • Hybrid: Mevcut monolith + yeni mikroservisler — kademeli geçiş (strangler fig)

  • Network izolasyonu kritik — her servisin DB'si kendi internal network'ünde

  • API Gateway (Traefik/Nginx) dış trafiği yönetir, routing, rate limiting, SSL

  • Monolith ile başla, domain oturunca ve ekip büyüyünce mikroservise geç

  • Anti-pattern'lerden kaçın: dağıtık monolith, nano-service, paylaşımlı DB