Mikroservisler ve Dağıtık Sistemler

Mikroservis mimarisinin temel prensipleri, dağıtık sistemlerin zorlukları (tutarlılık, ağ gecikmesi, hata yönetimi), servis keşfi, API Gateway, iletişim desenleri, veri yönetimi ve ölçeklenebilirlik stratejileri ele alınır.

Mikroservisler ve Dağıtık Sistemler

Mikroservisler ve Dağıtık Sistemler: Karmaşıklığı Yönetme Sanatı

Son yıllarda yazılım dünyasında monolitik mimarilerden mikroservislere doğru büyük bir dönüşüm yaşanıyor. Mikroservisler, büyük ve karmaşık uygulamaları daha küçük, bağımsız, dağıtılabilir servislere ayırarak esneklik, ölçeklenebilirlik ve ekip bağımsızlığı sağlar. Ancak bu avantajlar, dağıtık sistemlerin getirdiği zorluklarla birlikte gelir: ağ gecikmesi, veri tutarlılığı, hata yönetimi, servis keşfi ve izlenebilirlik gibi konular, mikroservis mimarisinin olmazsa olmazlarıdır. Bu yazıda, mikroservis mimarisinin temel prensiplerini, dağıtık sistemlerin zorluklarını ve bu zorlukların üstesinden gelmek için kullanılan desenleri ve .NET ekosistemindeki uygulamalarını detaylıca ele alacağız.


1. Monolith vs. Mikroservis: Neden Mikroservis?

Monolitik Mimari Mikroservis Mimarisi
Tek bir deploy ünitesi Her servis bağımsız deploy edilir
Tek bir teknoloji yığını Her servis farklı teknoloji kullanabilir
Tüm ekip aynı kod tabanında çalışır Her servisin kendi küçük ekibi olabilir
Ölçeklendirme tüm uygulama için yapılır Servis bazında ölçeklendirme yapılabilir
Değişiklikler riskli ve yavaştır Değişiklikler bağımsız ve hızlıdır
Hata tüm sistemi çökertir Hata sadece ilgili servisi etkiler

Mikroservislerin Avantajları:

  • Bağımsız Dağıtım (Independent Deployment): Her servis kendi CI/CD pipeline'ına sahiptir.

  • Teknoloji Çeşitliliği: Her servis, kendi ihtiyacına en uygun teknolojiyi kullanabilir.

  • Ekip Özerkliği: Ekipler, diğer ekiplerle koordinasyon ihtiyacı duymadan geliştirme yapabilir.

  • Sınırlı Hata Etkisi: Bir servisin çökmesi, diğer servisleri etkilemez (izolasyon).

  • Ölçeklenebilirlik: En yoğun servisler, diğerlerinden bağımsız olarak ölçeklendirilebilir.

Mikroservislerin Zorlukları:

  • Dağıtık Sistem Karmaşıklığı: Ağ gecikmesi, hata yönetimi, veri tutarlılığı gibi konular karmaşıktır.

  • Servis Keşfi ve Yük Dengeleme: Servislerin birbirini bulması ve isteklerin dengeli dağıtılması gerekir.

  • Veri Tutarlılığı: Distributed transaction'lar zordur; eventual consistency kabul edilmelidir.

  • Monitoring ve Observability: Çok sayıda servisi izlemek ve logları toplamak zordur.

  • Network Güvenliği: Servisler arası iletişimin güvenliği sağlanmalıdır.


2. Mikroservis Mimarisi: Temel Yapı Taşları

A. Servis İletişim Desenleri (Communication Patterns)

Desen Açıklama Kullanım Alanı .NET Uygulaması
Senkron (HTTP/gRPC) Servisler doğrudan birbirini çağırır (request/response). Basit sorgular, düşük gecikme gereksinimleri HttpClient, gRPC
Asenkron (Message Broker) Servisler olaylar (events) veya mesajlar üzerinden haberleşir. Uzun süren işlemler, gevşek bağlantı, event-driven mimari MassTransit, RabbitMQ, Azure Service Bus, Kafka
Event-Driven (Olay Tabanlı) Servisler, olayları (event) yayınlar ve dinler. CQRS, Event Sourcing, eventual consistency MassTransit, MediatR (In-Memory), Azure Event Grid

B. API Gateway (Tek Giriş Noktası)
API Gateway, tüm istemci isteklerini tek bir noktadan alır ve ilgili mikroservislere yönlendirir. Ayrıca, rate limiting, authentication, logging gibi cross-cutting concern'leri yönetir.

csharp

// YARP ile API Gateway Konfigürasyonu (Program.cs)
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

// appsettings.json
{
  "ReverseProxy": {
    "Routes": {
      "order-route": {
        "ClusterId": "order-cluster",
        "Match": { "Path": "/orders/{**catch-all}" }
      },
      "product-route": {
        "ClusterId": "product-cluster",
        "Match": { "Path": "/products/{**catch-all}" }
      }
    },
    "Clusters": {
      "order-cluster": {
        "Destinations": {
          "dest1": { "Address": "https://localhost:7001/" }
        }
      },
      "product-cluster": {
        "Destinations": {
          "dest1": { "Address": "https://localhost:7002/" }
        }
      }
    }
  }
}

C. Servis Keşfi (Service Discovery)
Servislerin birbirini bulması için Service Discovery (servis keşfi) kullanılır. Her servis, başlangıçta bir registry'ye (kayıt defteri) kaydolur; diğer servisler bu registry'den sorgulama yaparak hedef servisin adresini öğrenir.

  • Client-Side Discovery: İstemci, registry'ye sorgulama yapar ve doğrudan servisi çağırır.

  • Server-Side Discovery: API Gateway veya bir load balancer, registry'den sorgulama yapar ve isteği yönlendirir.

.NET'te Consul ile Servis Keşfi:

csharp

// Servis kaydı (Worker Service)
builder.Services.AddConsulServiceDiscovery(options =>
{
    options.Address = new Uri("http://localhost:8500");
    options.ServiceName = "order-service";
    options.ServiceAddress = new Uri("https://localhost:7001");
});

// Servis çağrısı (Diğer servisten)
var response = await _httpClient.GetAsync("http://order-service/orders/123");

3. Dağıtık Sistemlerin Zorlukları ve Çözümleri

Zorluk Çözüm Açıklama
Veri Tutarlılığı (Data Consistency) SAGA Pattern Distributed transaction'lar yerine, her adımda telafi (compensation) işlemleri ile eventual consistency sağlanır.
Ağ Hataları (Network Failures) Retry + Circuit Breaker Polly ile retry, timeout, circuit breaker politikaları uygulayın.
Servis Başarısızlığı (Service Failure) Bulkhead + Fallback Bulkhead ile kaynak izolasyonu, Fallback ile varsayılan yanıt döndürme.
Zaman Aşımı (Timeout) Timeout Policy Polly ile timeout politikası uygulayın (örn. 5 saniye).
Servis Keşfi Consul, Eureka, Kubernetes Servislerin başlangıçta registry'ye kaydolması ve birbirini bulması.
Güvenlik (Security) JWT, OAuth2, mTLS Servisler arası kimlik doğrulama ve yetkilendirme.
İzlenebilirlik (Observability) OpenTelemetry, Serilog Distributed tracing (Jaeger), metrics (Prometheus), logging (ELK).
Yük Dengeleme NGINX, HAProxy, Kubernetes Service Servisler arası trafiği dengeli dağıtmak.

Polly ile Dayanıklılık Sağlama:

csharp

var retryPolicy = Policy.Handle<HttpRequestException>()
    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

var circuitBreakerPolicy = Policy.Handle<HttpRequestException>()
    .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));

var timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(5));

var combinedPolicy = Policy.WrapAsync(timeoutPolicy, retryPolicy, circuitBreakerPolicy);

var response = await combinedPolicy.ExecuteAsync(async (ct) => 
    await _httpClient.GetAsync("https://order-service/orders/123", ct)
);

SAGA Pattern ile Distributed Transaction (Orchestration):

csharp

// MassTransit ile SAGA State Machine
public class OrderStateMachine : MassTransitStateMachine<OrderState>
{
    public State Pending { get; set; }
    public State Completed { get; set; }
    public State Cancelled { get; set; }

    public Event<OrderSubmittedEvent> OrderSubmitted { get; set; }
    public Event<PaymentProcessedEvent> PaymentProcessed { get; set; }
    public Event<StockReservedEvent> StockReserved { get; set; }

    public OrderStateMachine()
    {
        Initially(
            When(OrderSubmitted)
                .Publish(context => new ProcessPaymentCommand(context.Instance.OrderId))
                .TransitionTo(Pending)
        );

        During(Pending,
            When(PaymentProcessed)
                .Publish(context => new ReserveStockCommand(context.Instance.OrderId))
                .TransitionTo(Pending)
        );

        During(Pending,
            When(StockReserved)
                .Publish(context => new OrderCompletedEvent(context.Instance.OrderId))
                .Finalize()
        );

        During(Pending,
            When(StockReservationFailed)
                .Publish(context => new RefundPaymentCommand(context.Instance.OrderId))
                .TransitionTo(Cancelled)
        );
    }
}

4. Veri Yönetimi (Data Management)

Veritabanı Başına Servis (Database per Service): Her mikroservis, kendi veritabanına sahiptir. Bu, servislerin bağımsız olarak ölçeklenmesini ve veri modelini değiştirmesini sağlar.

Eventual Consistency (Nihai Tutarlılık): Distributed transaction'lar zordur. Bunun yerine, eventual consistency kabul edilir. Örneğin, bir sipariş oluşturulduğunda, stok servisi eventual olarak güncellenir.

Outbox Pattern: Veritabanı işlemi ile event göndermeyi atomik hale getirir.

csharp

// Outbox Pattern (MassTransit ile)
public async Task CreateOrderAsync(Order order)
{
    using var transaction = await _context.Database.BeginTransactionAsync();
    try
    {
        // 1. Siparişi kaydet
        _context.Orders.Add(order);
        await _context.SaveChangesAsync();

        // 2. Outbox event'ini kaydet (aynı transaction)
        var outboxEvent = new OutboxEvent
        {
            Id = Guid.NewGuid(),
            EventType = "OrderCreated",
            Payload = JsonSerializer.Serialize(order),
            CreatedAt = DateTime.UtcNow
        };
        _context.OutboxEvents.Add(outboxEvent);
        await _context.SaveChangesAsync();

        await transaction.CommitAsync();
    }
    catch
    {
        await transaction.RollbackAsync();
        throw;
    }
}

CQRS (Command Query Responsibility Segregation): Yazma (command) ve okuma (query) modellerini ayırarak ölçeklenebilirliği artırır.


5. Observability (Gözlemlenebilirlik)

Dağıtık sistemlerde, servisler arası iletişimi izlemek ve hataları tespit etmek çok önemlidir.

Üç Temel Sütun:

  1. Logging (Günlük Kaydı): Yapılandırılmış loglar (Serilog, NLog).

  2. Metrics (Ölçümler): Performans metrikleri (Prometheus, Application Insights).

  3. Distributed Tracing (Dağıtık İzleme): İsteklerin servisler arasındaki yolculuğunu izleme (Jaeger, Zipkin).

.NET ile OpenTelemetry Entegrasyonu:

csharp

builder.Services.AddOpenTelemetry()
    .WithTracing(tracer => tracer
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddJaegerExporter()
    )
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddPrometheusExporter()
    );

6. Dağıtık Sistemlerde Ölçekleme Stratejileri

  • Horizontal Scaling (Yatay Ölçekleme): Servis instance sayısını artırmak. Kubernetes, Docker Swarm gibi orchestration araçları ile yapılır.

  • Vertical Scaling (Dikey Ölçekleme): Servisin donanım kaynaklarını (CPU, RAM) artırmak.

  • Kubernetes HPA (Horizontal Pod Autoscaler): CPU veya custom metrics'e göre otomatik ölçekleme.

Kubernetes ile Ölçekleme:

yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: myregistry/order-service:latest
        ports:
        - containerPort: 80
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

7. Mikroservislerde Dağıtım Stratejileri

Strateji Açıklama Avantajları Zorlukları
Rolling Deployment Yeni sürümü kademeli olarak deploy et, eski instance'ları yavaşça azalt. Sıfır downtime, hata durumunda otomatik rollback. Uzun sürer, birden fazla sürüm aynı anda çalışır.
Blue-Green Deployment İki ortam (blue=mevcut, green=yeni), trafiği green'e yönlendir. Anlık geçiş, hızlı rollback. İki katı kaynak gerekir, veritabanı migration'ları zor.
Canary Deployment Yeni sürümü küçük bir kullanıcı grubuna aç (ör. %5). Risk azaltma, gerçek kullanıcı testi. Yönlendirme ve izleme karmaşıktır.
Feature Flag Yeni özelliği flag ile kontrol et, deploy'dan bağımsız aç/kapat. Zero-downtime, anlık rollback. Flag yönetimi ek karmaşıklık getirir.

8. .NET ile Mikroservis Geliştirme İpuçları

  1. Bağımlılıkları Soyutlayın: Servisler arası iletişim için interface'ler (contracts) tanımlayın. Bu sayede, servis implementasyonu değiştiğinde istemci etkilenmez.

  2. Configuration Yönetimi: AppSettings, Environment Variables, Azure App Configuration, Consul KV Store.

  3. Health Checks: Servislerin durumunu izlemek için /health endpoint'i ekleyin.

  4. Distributed Caching: Redis ile önbellekleme, veritabanı yükünü azaltır.

  5. Event Sourcing + CQRS: Karmaşık iş mantığı ve denetim gereksinimleri için.

  6. Message Broker Kullanımı: MassTransit ile RabbitMQ veya Azure Service Bus.

  7. Docker/Kubernetes: Konteynerleştirme ve orchestration.

Sonuç:

Mikroservisler, büyük ve karmaşık uygulamaları yönetilebilir parçalara ayırarak esneklik ve ölçeklenebilirlik sağlar. Ancak, dağıtık sistemlerin getirdiği zorluklar (veri tutarlılığı, ağ hataları, servis keşfi, izlenebilirlik) disiplinli bir yaklaşım ve doğru desenlerin kullanılmasını gerektirir.

.NET ekosistemi, mikroservis geliştirmek için zengin bir araç seti sunar: YARP (API Gateway), MassTransit (Message Bus), Polly (Resilience), OpenTelemetry (Observability), Kubernetes (Orchestration). Doğru planlama, desenler ve araçlarla, mikroservis mimarisi uzun vadede büyük kazanımlar sağlar. Unutmayın: Mikroservisler bir çözüm değil, bir organizasyon ve mühendislik disiplinidir.

Tüm yazılar