Mert Özen Her Satırda Daha İleri
Yazılım Mimarisi Messaging ve Event-Driven Architecture — #08

Saga Pattern: Choreography mi Orchestration mı?

Mert Özen 7 Kas 2026 13 dk 3 görüntülenme
Saga Pattern: Choreography mi Orchestration mı?

Saga, servisler arasına yayılan bir iş akışını tek bir distributed transaction yerine her biri kendi veritabanında commit edilen local transaction'lar dizisi olarak yürütür ve bir adım başarısız olursa öncekileri compensating transaction'larla anlamsal olarak geri alır. Garcia-Molina ve Salem'in 1987 makalesi, compensable/pivot/retryable adımlar, Bookstore sipariş saga'sının choreography ve orchestration sürümleri ve semantic lock.

Sürüm notu: Bu yazıdaki kodlar C# 14 ve .NET 10 ile derlenip denenmiştir.

Önce Tahmin Et

Bookstore'un checkout'u bugün tek bir method: siparişi oluştur, stok ayır, ödeme sağlayıcısından parayı çek, kargo firmasının API'sinde (Application Programming Interface) gönderi oluştur. Her satır aslında başka bir sisteme giden bir çağrı; ödeme sağlayıcısı ve kargo firması zaten Bookstore'un dışında.

namespace Bookstore.Sagas;

// Her satır aslında başka bir sistemin API'sine giden bir çağrı
public sealed class CheckoutService(
    OrderingService ordering, InventoryService inventory, PaymentService payments, ShippingService shipping)
{
    public string Checkout(OrderId id, IReadOnlyList<OrderedBook> books, decimal total)
    {
        ordering.Place(id);
        if (!inventory.TryReserve(id, books, out string? missing))
        {
            ordering.Cancel(id, "OutOfStock");
            return $"out of stock: {missing}";
        }
        if (!payments.TryCapture(id, total))
        {
            inventory.Release(id);
            ordering.Cancel(id, "PaymentFailed");
            return "payment failed";
        }
        ordering.MarkPaid(id);
        shipping.CreateShipment(id);
        return "confirmed";
    }
}

Kod hata durumlarını düşünmüş görünüyor: stok yoksa iptal, ödeme reddedilirse stoğu bırak ve iptal et. Peki kargo firmasının API'si timeout verirse ne olur? Ya da TryCapture'dan hemen sonra process yeniden başlarsa, kim, neyi, hangi sırayla geri alacağını nereden bilecek?

Saga: Tek Transaction Yerine Bir Dizi Local Transaction

Kargo API'sini kapatıp denedim:

1) Checkout as one method; the carrier API times out
  [Ordering ] order -> Placed
  [Inventory] reserved 2 x Dune
  [Payments ] captured 60.00 EUR
  [Ordering ] order -> Paid
  checkout threw: Carrier API timed out
  after the crash: stock [Dune 1, Middlemarch 5], captured 60.00 EUR, order Paid, shipments 0

Para çekildi, iki Dune ayrıldı, sipariş "ödendi" görünüyor ve gönderi yok. Exception method'dan çıktığı anda "şu ana kadar ne yaptım, sırada ne vardı" bilgisi de yok oldu; o bilgi yalnız call stack'teydi. Bu kod gizli bir saga: birden çok sistemi değiştiren, uzun sürebilen bir iş, ama ne durumunu saklıyor ne de yarıda kalırsa ne yapılacağını biliyor.

Kavramın kaynağı bir veritabanı makalesi: Hector Garcia-Molina ve Kenneth Salem'in 1987'de SIGMOD (Association for Computing Machinery'nin Special Interest Group on Management of Data) konferansında yayımlanan "Sagas" makalesi. Sorun, saatler ya da günler süren LLT'lerdi (long-lived transaction): lock'ları uzun süre tuttukları için diğer işleri bekletiyor, deadlock ve abort oranını artırıyorlardı. Önerileri, böyle bir işi kendi başına commit edilen alt transaction'lara (T1, T2, …, Tn) bölmek ve her birine onu anlamsal olarak geri alan bir compensating transaction (C1, C2, …) eşlemekti. Sistem iki sonuçtan birini garanti eder: ya T1…Tn'nin hepsi çalışır, ya da bir j noktasına kadar çalışanlar ters sırayla telafi edilir (T1…Tj, Cj…C1). Makalenin iki ayrıntısı bugün de geçerli. Birincisi, compensation veritabanını eski hâline döndürmez; uçak koltuğu örneğinde rezervasyonu iptal eden işlem, arada başkaları da rezervasyon yapmış olabileceği için koltuk sayısını eski değerine yazmaz, bir azaltır. İkincisi, saga'nın yarım kalmış sonuçlarını başka transaction'lar görebilir; izolasyon bilerek bırakılmıştır. Makale ayrıca saga'nın durumunu bir log'a yazan bir koordinatör (SEC, saga execution coordinator) tarif eder: çökmeden sonra log okunur ve yarım kalan saga ya geri sarılır ya da kaldığı yerden sürdürülür (forward recovery).

Microservices dünyası bu fikri bir veritabanının içinden alıp servisler arasına taşıdı. Microsoft Learn'deki Saga pattern sayfası tanımı şöyle özetliyor: her adım kendi servisinin veritabanında atomik bir local transaction, bir sonraki adımı bir event ya da message ile tetikliyor, bir adım başarısız olursa önceki adımların compensating transaction'ları çalışıyor. Bookstore bugün tek veritabanlı bir monolith olsa da saga'ya ihtiyacı var, çünkü ödeme sağlayıcısı ve kargo firması o veritabanının transaction'ına hiçbir zaman katılmayacak.

Compensable, Pivot ve Retryable Adımlar

Her adım aynı değil. Microsoft Learn'deki sayfa ve Chris Richardson'ın Microservices Patterns (2018) kitabı adımları üç gruba ayırıyor. Compensable adım, karşıt etkili bir işlemle geri alınabilen adım. Pivot adım, saga'nın geri dönüşü olmayan noktası: o başarılı olduktan sonra artık geri sarılmaz, ileri gidilir. Retryable adım, pivot'tan sonra gelen ve sonunda başarılı olacağı varsayılan, bu yüzden idempotent olması gereken adım. Bookstore'un sipariş saga'sı şöyle:

AdımSahibiTürCompensation
Siparişi oluştur (Placed)OrderingCompensableSiparişi iptal et (Cancelled)
Stok ayırInventoryCompensableRezervasyonu bırak
Ödemeyi çek (capture)PaymentsPivotYok; sonrası ileri gider
Gönderi oluşturShippingRetryableGerekmez; başarılı olana kadar tekrar
Onay e-postasıNotificationRetryableGerekmez

Pivot'un nerede olduğu teknik değil, bir iş kararı. Parayı iade etmek mümkün, ama refund müşterinin ekstresinde iki satır, ödeme sağlayıcısına komisyon ve bir destek talebi demek; Bookstore "ödeme alındıysa kitabı göndeririz" diyerek capture'ı geri dönüşsüz kabul ediyor. Bu kararın tasarıma iki etkisi var. Başarısız olma ihtimali yüksek ve ucuz geri alınabilen adımlar pivot'tan önce gelmeli: stok yoksa bunu para çekmeden öğrenmek istiyoruz. Pivot'tan sonraki her adım ise "başarısız olursa ne yaparız" sorusunu "başarılı olana kadar nasıl tekrar ederiz" sorusuyla değiştirmeli; kargo API'si bir saat kapalı kalırsa sipariş iptal edilmez, gönderi bir saat sonra oluşturulur. Açılıştaki koddaki asıl hata da buydu: pivot'tan sonraki bir adımın hatasını, geri sarılacak bir hata gibi exception'la yukarı fırlatıyor ve durumu hiçbir yere yazmıyordu. Ödeme tarafında pivot'u daha geç bir noktaya itmenin bilinen bir yolu da var: kartta önce yalnız authorize edip (karşılığı provizyonu bozmak, void) capture'ı gönderi oluştuğunda yapmak. O zaman authorize compensable bir adım olur, capture en sona kayar.

Compensation Rollback Değildir

Garcia-Molina'nın koltuk örneği bugünkü sistemlerde daha da belirgin. Compensation bir "undo" değil, iş dünyasında anlamı olan yeni bir işlem. Gönderilmiş bir e-posta geri alınamaz, ancak ikinci bir "siparişiniz iptal edildi" e-postası gönderilir. Refund, capture'ın silinmesi değil, ayrı bir para hareketi. Stok rezervasyonunu bırakmak, Reserved'ı eski değerine yazmak değil, rezerve edilen miktarı geri eklemek; çünkü arada başka siparişler de rezervasyon yapmış olabilir.

Bu yüzden compensation'ın üç özelliği olmalı. İdempotent olmalı: compensation message'ı da at-least-once teslim edilir ve iki kez gelebilir. Örnekteki InventoryService.Release rezervasyon kaydı yoksa hiçbir şey yapmıyor. Yalnız var olanı geri almalı: ödeme adımına hiç gelmemiş bir saga'da "refund" çağırmak, hiç çekilmemiş bir tutarı iade etmeye çalışmak demek. Ve sıradan bağımsız olmalı: message'ların sırası garanti olmadığı için "rezervasyonu bırak" komutu bazen rezervasyonun kendisinden önce gelebilir. O durumda bırakılacak bir şey yok diye sessizce geçmek, rezervasyon birkaç milisaniye sonra geldiğinde stoğu sonsuza kadar kilitler; sağlam çözüm "bu sipariş iptal edildi" bilgisini saklayıp sonradan gelen rezervasyonu reddetmektir.

Choreography: Event'lerle Koordinasyon

Saga'yı yürütmenin ilk yolu choreography: merkezi bir koordinatör yok, her servis başka servislerin event'lerini dinleyip kendi local transaction'ını yapıyor ve sonucunu yine bir event olarak yayınlıyor. Aynı akışı, message'ları bir queue'dan sırayla teslim eden küçük bir in-memory bus ile kurdum:

public static class Choreography
{
    public static void Wire(InMemoryBus bus, OrderingService ordering, InventoryService inventory,
        PaymentService payments, ShippingService shipping)
    {
        // Ordering: siparişin durumunu başka context'lerin event'lerinden çıkarır
        bus.Subscribe<StockReserved>(e => ordering.MarkPaymentPending(e.OrderId));
        bus.Subscribe<PaymentCaptured>(e => ordering.MarkPaid(e.OrderId));
        bus.Subscribe<PaymentFailed>(e => ordering.Cancel(e.OrderId, "PaymentFailed"));
        bus.Subscribe<OutOfStock>(e => ordering.Cancel(e.OrderId, "OutOfStock"));

        // Inventory: siparişi duyunca stok ayırır, ödeme başarısızsa bırakır (compensation)
        bus.Subscribe<OrderPlaced>(e => bus.Publish(inventory.TryReserve(e.OrderId, e.Books, out string? missing)
            ? new StockReserved(e.OrderId)
            : new OutOfStock(e.OrderId, missing!)));
        bus.Subscribe<PaymentFailed>(e => inventory.Release(e.OrderId));

        // Payments: tutarı OrderPlaced'ten öğrenir, Inventory'nin StockReserved'ı gelince çeker
        Dictionary<OrderId, decimal> amounts = [];
        bus.Subscribe<OrderPlaced>(e => amounts[e.OrderId] = e.Total);
        bus.Subscribe<StockReserved>(e => bus.Publish(payments.TryCapture(e.OrderId, amounts[e.OrderId])
            ? new PaymentCaptured(e.OrderId, amounts[e.OrderId])
            : new PaymentFailed(e.OrderId, "card declined")));

        // Shipping: ödeme alınınca gönderi oluşturur
        bus.Subscribe<PaymentCaptured>(e => bus.Publish(new ShipmentCreated(e.OrderId, shipping.CreateShipment(e.OrderId))));
    }
}
Choreography: 3 x Middlemarch, 135.00 EUR
  [Ordering ] order -> Placed
  OrderPlaced
  [Inventory] reserved 3 x Middlemarch
  StockReserved
  [Ordering ] order -> PaymentPending
  [Payments ] declined 135.00 EUR
  PaymentFailed
  [Ordering ] order -> Cancelled (PaymentFailed)
  [Inventory] released 3 x Middlemarch
  result: order Cancelled, stock [Dune 3, Middlemarch 5], captured 0.00 EUR

Ödeme sağlayıcısı 100 EUR üzerini reddediyor; 135 EUR'luk sipariş iptal edildi, üç Middlemarch geri bırakıldı. Kod kısa ve hiçbir ek bileşen yok. Ama dikkatli okuyunca bedeli görünüyor. Payments, Inventory'nin StockReserved event'ini bilmek zorunda; tutarı da OrderPlaced'ten kendisi saklıyor, çünkü stok event'i ödeme tutarını taşımıyor (taşısaydı Inventory'nin contract'ı ödemeyle ilgili bir alana bağımlı olurdu). Akış hiçbir yerde yazılı değil; "ödeme hangi koşulda çekilir?" sorusunun cevabı dört farklı subscription'a dağılmış durumda. Yeni bir adım eklemek, örneğin ödemeden önce bir fraud kontrolü, Payments'ın hangi event'i dinlediğini değiştirmek demek; yani bir adımı eklemek için komşusuna dokunmak gerekiyor.

Orchestration: Merkezi Bir Koordinatör

İkinci yol orchestration: akışı bilen tek bir bileşen, orchestrator, katılımcılara ne yapacaklarını command'larla söylüyor ve cevaplarını event olarak alıyor. Hohpe ve Woolf'un EIP (Enterprise Integration Patterns, 2003) kataloğunda bunun adı Process Manager. Aynı saga, bu kez Ordering'in içinde yaşayan bir OrderSaga ile:

08-saga-styles-tr.png

public enum SagaStep { ReservingStock, CapturingPayment, CreatingShipment, ReleasingStock, Completed, Cancelled }

// Orchestrator: akışın tamamı tek bir yerde; katılımcılar yalnız kendi command'larını bilir
public sealed class OrderSaga(InMemoryBus bus, OrderingService ordering)
{
    private readonly Dictionary<OrderId, (SagaStep Step, decimal Total)> _sagas = []; // 09'da veritabanına taşınacak

    public void Handle(object message)
    {
        switch (message)
        {
            case OrderPlaced e:
                _sagas[e.OrderId] = (SagaStep.ReservingStock, e.Total);
                Log.Write("OrderSaga", "started -> ReservingStock");
                bus.Publish(new ReserveStock(e.OrderId, e.Books));
                break;
            case StockReserved e:
                MoveTo(e.OrderId, SagaStep.CapturingPayment);
                ordering.MarkPaymentPending(e.OrderId);
                bus.Publish(new CapturePayment(e.OrderId, _sagas[e.OrderId].Total));
                break;
            case OutOfStock e:
                MoveTo(e.OrderId, SagaStep.Cancelled);
                ordering.Cancel(e.OrderId, "OutOfStock");
                break;
            case PaymentCaptured e:
                MoveTo(e.OrderId, SagaStep.CreatingShipment);
                ordering.MarkPaid(e.OrderId);
                bus.Publish(new CreateShipment(e.OrderId));
                break;
            case PaymentFailed e:
                MoveTo(e.OrderId, SagaStep.ReleasingStock);
                bus.Publish(new ReleaseStock(e.OrderId));
                break;
            case StockReleased e:
                MoveTo(e.OrderId, SagaStep.Cancelled);
                ordering.Cancel(e.OrderId, "PaymentFailed");
                break;
            case ShipmentCreated e:
                MoveTo(e.OrderId, SagaStep.Completed);
                break;
        }
    }

    private void MoveTo(OrderId id, SagaStep next)
    {
        Log.Write("OrderSaga", $"{_sagas[id].Step} -> {next}");
        _sagas[id] = _sagas[id] with { Step = next };
    }
}
Orchestration: 3 x Middlemarch, 135.00 EUR
  [Ordering ] order -> Placed
  OrderPlaced
  [OrderSaga] started -> ReservingStock
  ReserveStock
  [Inventory] reserved 3 x Middlemarch
  StockReserved
  [OrderSaga] ReservingStock -> CapturingPayment
  [Ordering ] order -> PaymentPending
  CapturePayment
  [Payments ] declined 135.00 EUR
  PaymentFailed
  [OrderSaga] CapturingPayment -> ReleasingStock
  ReleaseStock
  [Inventory] released 3 x Middlemarch
  StockReleased
  [OrderSaga] ReleasingStock -> Cancelled
  [Ordering ] order -> Cancelled (PaymentFailed)
  result: order Cancelled, stock [Dune 3, Middlemarch 5], captured 0.00 EUR

Sonuç aynı, message sayısı daha fazla: her adım bir command ve bir cevap. Karşılığında akış tek bir switch'te okunuyor, saga'nın her an hangi adımda olduğu (CapturingPayment, ReleasingStock) bir değer olarak var ve katılımcılar birbirinden habersiz. Inventory artık Payments'ı, Payments da Inventory'yi tanımıyor; ikisi de yalnız kendi command'ını ve cevabını biliyor. Fraud kontrolü eklemek, OrderSaga'ya bir adım eklemek demek. Örnekte saga'nın durumu bir Dictionary'de; process yeniden başlarsa açılıştaki koddan farkı kalmaz. Durumu veritabanında saklamak, timeout ve eşzamanlı message'lar bir sonraki yazının konusu.

Hangisi Ne Zaman?

ChoreographyOrchestration
Akışın yeriSubscription'lara dağılmışOrchestrator'da, tek yerde
CouplingHer servis başkalarının event'lerini bilirOrchestrator katılımcıların command'larını bilir; katılımcılar birbirini bilmez
Yeni adımKomşu servislerin subscription'ları değişirOrchestrator'da bir adım
Ek bileşenYokPersist edilen bir orchestrator
"Bu sipariş nerede kaldı?"Log'lardan ve correlation id'den yeniden kurulurSaga state'inde bir satır
RiskDöngüsel bağımlılık, görünmeyen iş akışıHer şeyi bilen, iş kuralı biriktiren bir orchestrator

Microsoft Learn'in özeti de benzer: choreography az servisli, basit akışlarda iyi; adım ve servis sayısı arttıkça orchestration'ın tek yerde duran mantığı ağır basıyor. Pratikte iyi işleyen bir kural, iki ya da üç adımlı, tek ekibin sahip olduğu ve compensation'ı basit bir akışta choreography; pivot'u, timeout'u, birden fazla compensation yolu olan ya da iş birimlerinin "sipariş şu an hangi adımda?" diye sorduğu bir akışta orchestration. İkisi bir arada da kullanılır: Bookstore'un sipariş saga'sı Ordering'in içinde bir orchestrator olur, ama Notification gibi akışı etkilemeyen tepkiler hâlâ OrderPlaced gibi event'lere choreography ile bağlanır. Orchestrator'ın şişmesine karşı tek kural var: orchestrator yalnız sırayı ve kararı bilmeli; "stok nasıl ayrılır", "kargo ücreti nasıl hesaplanır" gibi iş kuralları katılımcıların kendi modelinde kalmalı.

İzolasyon Yok: Saga Anomalileri ve Karşı Önlemler

ACID'in (Atomicity, Consistency, Isolation, Durability) "I"sı saga'da yok: her adım kendi transaction'ını commit ediyor ve sonuçları bütün saga bitmeden başkalarına görünüyor. Microsoft Learn'in sayfası bundan doğan üç anomaliyi sayıyor. Lost update: bir saga, başka bir saga'nın değişikliğini görmeden üstüne yazar. Dirty read: bir saga, henüz bitmemiş ve belki compensation ile geri alınacak bir değişikliği okur; Bookstore'da ödemesi birkaç saniye sonra reddedilecek bir siparişin ayırdığı son Middlemarch yüzünden başka bir müşteriye "stokta yok" demek bunun tipik örneği. Fuzzy (nonrepeatable) read: aynı saga'nın iki adımı arada değişmiş farklı değerler okur.

Aynı sayfa karşı önlemleri de listeliyor. Semantic lock: compensable adım kaydın üzerine "işlem sürüyor" diyen uygulama seviyesinde bir işaret koyar; Bookstore'da PaymentPending durumu tam olarak bu. Commutative update: güncellemeleri sıradan bağımsız tasarlamak; stok için "değeri yaz" yerine "şu kadar ekle/çıkar". Pessimistic view: saga'yı yeniden sıralayıp riskli güncellemeleri retryable adımlara taşımak. Reread value: güncellemeden önce değerin değişmediğini kontrol etmek, bir tür optimistic concurrency; ödeme çekmeden önce fiyatın hâlâ geçerli olduğunu doğrulamak gibi. Version file: kayda yapılan işlemlerin log'unu tutup sıra dışı gelenleri doğru sıraya koymak. Risk-based concurrency: değere göre yöntem seçmek; düşük riskli işlerde saga, yüksek riskli olanlarda daha sıkı bir mekanizma.

Semantic lock'un Bookstore'daki karşılığı küçük bir guard:

public void CancelByCustomer(OrderId id)
{
    // Semantic lock: saga ödeme adımındayken müşteri siparişi iptal edemez
    if (Orders[id] is OrderStatus.PaymentPending)
        throw new DomainException("order.payment-pending", "Payment is in progress; the order cannot be cancelled right now.");
    Cancel(id, "CustomerRequest");
}
Semantic lock: the customer cancels while payment is in progress
  [Ordering ] order -> Placed
  [Ordering ] order -> PaymentPending
  order.payment-pending: Payment is in progress; the order cannot be cancelled right now.

Semantic lock olmasaydı müşteri siparişi iptal eder, Inventory stoğu bırakır, birkaç yüz milisaniye sonra ödeme sağlayıcısından PaymentCaptured gelirdi: iptal edilmiş bir sipariş için çekilmiş para. Semantic lock'un bedeli kullanıcı deneyimine yansır: "şu an iptal edemezsin" demek yerine iptal isteğini kaydedip saga pivot'u geçtikten sonra refund ile işlemek daha nazik bir çözüm; ama o da saga'ya bir yol daha ekler. Ayrıca lock'un bir sahibi ve süresi olmalı: saga çökerse PaymentPending'de kalan sipariş sonsuza kadar kilitli kalmamalı. Bunu timeout'larla çözmek bir sonraki yazının konusu.

Sık Düşülen Tuzaklar

Saga'yı ACID transaction gibi sunmak

Kullanıcıya pivot'tan önce "siparişin onaylandı" demek, saga birkaç saniye sonra compensation ile geri sarıldığında yalan söylemek olur. Saga sürerken kullanıcıya gösterilecek durum ara bir durumdur (Placed, PaymentPending); arayüz bunu "işleniyor" olarak göstermeli, sonucu polling, e-posta ya da push ile bildirmeli.

Pivot'tan sonra hata fırlatmak

Açılıştaki kod bunu yapıyordu. Pivot'tan sonraki adımların başarısızlığı geri sarma sebebi değil, tekrar deneme sebebidir. Kargo API'si kapalıysa saga "gönderi bekliyor" adımında kalmalı ve tekrar denemeli; tekrar sayısı tükenirse bir insan devreye girmeli.

Compensation'ı mutlu yol kadar test etmemek

Compensation kodu nadiren çalıştığı için en az test edilen koddur ve tam da sistem zaten sorunluyken çalışır. Her compensable adım için "adım başarılı, sonraki adım reddedildi" senaryosu, compensation'ın iki kez gelmesi ve asıl işlemden önce gelmesi test edilmeli.

Choreography'de döngü

Inventory PaymentFailed'i, Payments StockReleased'ı, Ordering ikisini birden dinlemeye başladığında event'ler birbirini tetikleyen bir döngüye dönebilir. Microsoft Learn de choreography'nin dezavantajları arasında katılımcılar arası döngüsel bağımlılık riskini sayıyor. Event akışının bir diyagramı yoksa çizmek, döngüyü görmenin en ucuz yolu.

Her şey için saga

Aynı veritabanında, aynı aggregate'in ya da aynı bounded context'in içinde biten bir işlem için saga kurmak, bedava olan ACID garantisini atıp yerine eventual consistency, compensation ve anomali yönetimi almak demek.

Ne Zaman Kullanma

Bütün adımlar aynı veritabanındaysa local transaction kullan. DDD (Domain-Driven Design) serisinin "Domain Event'ler" yazısında stok ayırmayı siparişle aynı transaction'a koymuştuk; Bookstore tek veritabanında kaldıkça bu, iki adımlı bir saga'dan hem daha basit hem daha doğru. Compensation'ı tanımlanamayan adımlar da saga'ya uymaz: Microsoft Learn bu pattern'in sıkı bağlı transaction'larda, compensation'ın erken katılımcılarda gerektiği ve döngüsel bağımlılık olan durumlarda uygun olmayabileceğini söylüyor. Ve iş gerçekten "ya hep ya hiç" gerektiriyorsa (iki hesap arasında para transferi gibi), eventual consistency'yi kabul etmek yerine iki tarafı aynı veritabanında tutacak şekilde sınırı yeniden çizmek çoğu zaman daha iyi bir karardır.

Küçük Bir Deneme

★☆☆ Choreography örneğinde stok yetmeyen bir sipariş ver (4 x Dune). Hangi event'ler yayınlanıyor, Payments bu siparişi hiç görüyor mu? Payments'ın amounts dictionary'sinde ne kalıyor ve bu neden küçük bir bellek sızıntısı?

★★☆ Orchestration örneğine pivot'tan önce bir CheckFraud adımı ekle. Kaç dosya değişti? Aynı adımı choreography'ye eklemek için hangi subscription'ları değiştirmen gerekiyor?

★★★ ReleaseStock command'ının ReserveStock'tan önce teslim edildiği bir sıra kur (bus'a iki message'ı ters sırayla koy). InventoryService.Release ne yapıyor, rezervasyon sonra gelince stoğa ne oluyor? Release'i iptal edilen siparişleri hatırlayacak şekilde değiştir.

Örnekteki OrderSaga bütün kararları doğru veriyor ama durumunu bellekte tutuyor; açılıştaki kod kadar kırılgan. Bir sonraki yazıda onu Entity Framework Core (EF Core) ile persist edilen bir state machine'e çevirecek, correlation id ile message eşlemeyi, optimistic concurrency'yi, timeout'ları ve compensation'ın kendisinin başarısız olduğu durumu ele alacağız; aynı saga'nın MassTransit ve Wolverine'deki karşılığına da bakacağız.