Microservice mimarilerde “dağıtık transaction” lafı geçtiği anda saga pattern gündeme geliyor. Orchestration, choreography, compensating actions, state machines. Karmaşık, ciddi bir operational yük.
Birçok projede saga’nın getirdiği karmaşıklık gerektirmiyor. Basit bir compensating action yaklaşımı yeterli. Bu yazıda daha hafif alternatifleri anlatacağım.
Neden two-phase commit olmaz?
Monolith’te database transaction kullanırsın. Commit veya rollback. Kolay. Ama microservice mimarisinde 3 farklı servisin 3 farklı database’i var. Two-phase commit (2PC) pattern’i teorik olarak çözüm sunuyor ama pratikte:
- Tüm servisler 2PC’i desteklemeli (çoğunluk desteklemez)
- Coordinator down olursa sistem locked kalıyor
- Performance çok düşük (her transaction 2 round trip)
- Cloud native environment’larda fail-prone
Bu yüzden 2PC modern microservice’larda neredeyse hiç kullanılmıyor. Alternatif lazım.
Saga pattern çözümü
Saga ile her adımı sequential olarak uyguluyorsun. Bir adım fail ederse önceki adımların “compensating action”larını çalıştırıp geri al.
Sipariş akışı örneği:
1. Order create (Order servisi)
2. Payment authorize (Payment servisi)
3. Inventory reserve (Inventory servisi)
4. Shipping schedule (Shipping servisi)
Adım 3’te fail olursa: payment void + order cancel. Compensating actions.
Saga 2 variant’ta gelir:
Orchestration: Merkezi bir orchestrator her adımı organize eder, state’i tutar. Clean ama single point of failure.
Choreography: Her servis kendisi dinleyici. Event’lere reaksiyon veriyor. Decoupled ama logic akışını takip etmek zor.
İkisi de implementation complexity’si yüksek.
Daha basit alternatif: compensating actions + eventual consistency
Çoğu business senaryoda saga’nın tam karmaşıklığı gerekmiyor. Şu pattern’ı kullanıyorum:
1. Local transaction önce. Servisinin kendi transaction’ında ne yapabiliyorsan yap. Database’e yaz, event publish et.
2. Event publishing outbox pattern’iyle. Aynı transaction’da hem business data’yı hem event’i veritabanına yaz. Sonra ayrı bir process event’i queue’ya aktarıyor (CDC veya polling).
3. Diğer servisler event dinliyor, kendi iş akışını yapıyor. Bir tanesi fail ederse, compensating event yayınlıyor.
4. Monitoring + alerting. Sync’te kalmayan state’leri otomatik detect ediyorsun, human intervention için alert.
Bu yaklaşım saga’nın %80’ini, implementation complexity’nin %30’uyla veriyor.
Gerçek proje örneği
Parademi’de abonelik upgrade akışı şöyle:
- Kullanıcı upgrade butonuna basıyor. Payment servisi yeni subscription charge yapıyor. Başarılı olursa
subscription.upgradedevent publish ediyor. - User service event’i dinliyor, kullanıcının plan’ını günceliyor.
- Notification service event’i dinliyor, kullanıcıya email gönderiyor.
- Analytics service event’i dinliyor, upgrade metric’ini kaydediyor.
Payment başarılıysa adım 2-4 paralel ve bağımsız çalışıyor. Her biri kendi retry logic’iyle. Biri fail olursa diğerleri etkilenmiyor.
Eğer payment fail olursa event yok, hiçbir şey değişmiyor. Kullanıcı hata görüyor, tekrar deniyor.
Bu bir “saga” değil teknik anlamda. Compensating action’a gerek yok çünkü her adım idempotent ve bağımsız. Karmaşıklık minimum.
İdempotency olmazsa olmaz
Bu yaklaşımın temeli: her consumer idempotent olmalı. Aynı event’i 2 kez alırsan aynı sonuç. Bunu sağlamak için:
Event ID’si deduplicate edilsin. Consumer tarafında işlenmiş event ID’lerini sakla. Duplicate geldiğinde skip et.
Natural idempotency. “User plan’ını pro’ya güncelle” işlemi 2 kere çalışsa da sonuç aynı. Ama “user’a +1000 credit ekle” değil. Mümkünse natural idempotent operasyonlar tasarla.
Outbox pattern. Event publishing’i business transaction’ın içine al. Partial state’i önle.
Compensating action ne zaman?
Compensating action gerçekten gerekli olduğu durumlar var:
1. Irreversible side effect’ler. Fiziksel kargo gönderildikten sonra “cancel” edemezsin. Compensating action: “iade etiketi gönder”, para iade et.
2. External service call’ları. Third-party API’ya çağrı yaptın, onlar bir şey yaptı. Compensating: onları da çağır ve geri al (varsa API’leri).
3. Multi-step business process. “Çok adımlı booking akışı – otel + uçak + araba kiralama” gibi. Bir tanesi fail ederse diğerlerini compensate et.
Bu 3 senaryoda saga pattern’ı mantıklı. Geri kalanda basit event-driven yaklaşım yeterli.
Monitoring kritik
Compensating action olmayan yaklaşımda en önemli şey visibility. “Event publish oldu ama consumer işlemedi” durumunu nasıl detect ediyorsun?
- Event publish ve consume metric’leri. Publishing rate = consume rate olmalı. Sapmalar gecikme veya fail gösteriyor.
- Dead letter queue monitoring. Failed event’ler DLQ’ya gidiyor. DLQ boş değilse ilgilen.
- Consistency reports. Periyodik olarak kritik state’leri farklı servislerde karşılaştır. Mismatch varsa alert.
- User-facing error rates. “Upgrade oldu ama plan pro’ya dönmedi” şikayet sayısını izle.
Bu monitoring olmadan eventual consistency bir süre sonra eventual inconsistency oluyor.
Sonuç
Saga pattern güçlü ama çoğu senaryo için overkill. Event-driven compensating action’larla simpler bir yaklaşım %80 senaryoyu çözüyor. Idempotent consumer’lar, outbox pattern, iyi monitoring bu yaklaşımın üç ayağı.
Gerçekten complex distributed workflow’un varsa saga’ya geç. Ama basit asenkron iş akışları için gereksiz complexity’den uzak dur.