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:
-
Logging (Günlük Kaydı): Yapılandırılmış loglar (Serilog, NLog).
-
Metrics (Ölçümler): Performans metrikleri (Prometheus, Application Insights).
-
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ı
-
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.
-
Configuration Yönetimi: AppSettings, Environment Variables, Azure App Configuration, Consul KV Store.
-
Health Checks: Servislerin durumunu izlemek için
/healthendpoint'i ekleyin. -
Distributed Caching: Redis ile önbellekleme, veritabanı yükünü azaltır.
-
Event Sourcing + CQRS: Karmaşık iş mantığı ve denetim gereksinimleri için.
-
Message Broker Kullanımı: MassTransit ile RabbitMQ veya Azure Service Bus.
-
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.