Dağıtık mimarilerde varolan, mikroservis mimarisinin popüler hale gelmesiyle birlikte birçok insanın dikkatini tekrar çeken bir sorun hakkında konuşuyor olacağız. Sorundan bahsedecek olursak bir işlemin birden fazla servis tarafından yerine getirilmesi gereken adımlara sahip olmasıdır.
Öncelikli olarak işlem dediğimiz zaman ne düşünmemiz gerektiğiyle işe başlayalım. İşlem, birbirine bağımlı adımlar grubudur. Adımların hepsinin tamamlanması ile işlem tamamlanır. Bir adım bile hata alırsa, tüm adımlar geri alınır. Bu durum göz önüne alındığında aşikâr olan şu sözü söyleyebiliriz; işlem ya yapılmıştır ya da yapılmamıştır. Bir ayakkabıyı ya almışızdır ya da almamışızdır. İşlemin kısmen yapılması gibi bir şey söz konusu değildir.
Bir mağazaya girdiğinizi düşünelim. Vitrinde gördüğünüz ayakkabının sizin ayak numaranıza göre olanını istediniz. Depodan istediğiniz ayakkabı getirildi ve kasaya geçtiniz. Kredi kartınızı verdiniz ve gerekli ücret çekildi. Ardından faturanız kesildi ve ayakkabınızı alıp çıktınız. Tüm adımlar başarı ile tamamlandı ve ayakkabıyı alma işleminiz başarıyla sonlandı. Senaryoyu bir de şu şekilde düşünelim; kredi kartınızdan para çekilemedi. Bu durumda fatura kesilmeye çalışılmayacak ve ürün depoya geri gönderilecek. Yani adımlardan biri bile başarısız olduğunda, tamamlanan adımlar da geri alınacak.
Monolith kod geliştirme pratiğinde iş bütünlüğünün sağlanması için başvurulan en kolay çözüm ilişkisel veri tabanlarında yer alan işlem (Database Transaction) özelliğidir. Örneğin stoktaki ürün sayısı azaltılırken müşteriden alınan ücrete ait veri sisteme eklenmektedir.
Mikroservis mimarisi gibi dağıtık sistemlerde bu yönetimin uygulanabilmesi pek olası değildir. Bunun bir sebebi servislerin ayrı veri tabanları kullanıyor olmasıdır. Bir diğer sebebi ise servislerden bir veya birkaçının belki de ilişkisel veri tabanı kullanmıyor oluşu olacaktır. Bu soruna karşı 2PC (two phase commit) ve Saga gibi iki ana çözüm ortaya çıkmıştır.
Bu link aracılığı ile Saga konusunda hazırladığım yazı ve örneğe erişebilirsiniz.
2PC Nedir?
2PC’yi bir koordinatör ve n tane modülden oluşan yapı olarak tanımlayabiliriz. Koordinatör yürütülmesi gereken adımlar için alt modülleri çağırarak hazırlık yaptırır. Tüm modüller hazır olduğuna dair işaret verdikten sonra koordinatör tüm alt sistemlere sinyal göndererek tamamlanan hazırlıkların sisteme işlenmesini söyler. Onay sinyalinden sonra koordinatör sonucu istemciye (client) döner.
Mağazaya girip ayakkabı satın almak istediğiniz zaman sizi bir koordinatör karşılar. Koordinatör, depo sorumlusundan ayakkabıyı hazırlamasını, kasiyerden parayı çekmesini, muhasebeden faturayı kesmesi ister. Tüm görevliler hazır olduklarını söylerlerse koordinatör herkese işlemlerin başarıyla sonlandırmalarını ve kayıt altına almalarını söyler. Sonunda koordinatör size döner ve ayakkabıyı satın aldığınızı bildirir. Bir de kredi kartınızda yeteri kadar bakiye olmadığı durumu hayal edelim. Muhasebe sorumlusu ve depo sorumlusu hazır olduklarına dair, kasa görevlisi ise bu satın alma işlemi için uygun durumu yaratamadığına dair bilgiyi koordinatöre iletecekti. İşlem adımlarından biri yerine getirilemediği için koordinatör muhasebe sorumlusuna ve depo sorumlusuna yaptıkları hazırlıkları iptal etmeleri gerektiğini bildirecekti. Sonunda da koordinatör size dönüp ürünü satın alamadığını bildirecekti.
2PC protokolünün implemente edilmesi karmaşıktır. Ayrıca adımların hepsi tamamlanana kadar istemci bekletilir. Bazı süreçler uzun vakit alır ve kullanıcının bekletilmesi mümkün olmaz. Bu gibi durumlarda Saga tasarım desenini kullanıyor olacağız.
Saga Nedir?
Saga, tek bir uzun işlem yerine alt işlemlerin peş peşe çağırılması temeline dayanan bir tasarım desenidir.
Her bir adım durum (state) olarak değerlendirilir.
Satış işlemi örneğinden devam edelim. Adımların başarılı bir şekilde tamamlandığı durumda aşağıdaki gibi bir akışa sahip olacağımızı hayal edebiliriz.
Alt işlemlerden birinin tamamlanamaması durumunda tüm adımların geriye alınacağından bahsetmiştik. Faturanın kesilemediği durumu örneklersek aşağıdaki gibi bir akışa sahip olacağımızı hayal edebiliriz.
Koreografi tabanlı Saga (Choreography based Saga): Sistemde ortaya çıkan olay (event) mesajlarının servisler tarafından kullanılması ile sürecin ilerlemesi mantığına dayanır.
Mağaza örneğimiz üzerinden böyle bir sistem hayal etmeye çalışalım. Müşteri gelip bir sipariş bildirir. SiparişOluştu olayı bu şekilde ortaya çıkar. SiparişOluştu olayını gözleyen stok servisimiz ürünün stoktaki sayısını azaltır. StokRezerveEdildi olayı ortaya çıkmış olur ve bu olayı gözleyen ödeme servisi ödeme alma girişiminde bulunur. Ödemenin başarısız olması halinde sistemde ÖdemeTamamlanamadı olayı ortaya çıkar. ÖdemeTamamlanamadı durumunu izleyen stok servisi rezervasyon işlemini iptal eder. Ödeme servisi fatura kesilememesi halinde de ÜcretİadeEdildi olayını ortaya çıkarmak zorundadır. Stok servisimiz ücret iade işlemini de takip ederek stok üzerindeki rezervasyonu kaldırmak durumundadır.
Birkaç adımdan oluşan işlemler için yürütülmesi kolay bir yöntem olarak görülebilir fakat yukarıdaki örnekten de anlayabileceğimiz gibi işler kısa sürede karmaşık bir hale gelebilir. Adım sayınız artmaya başladıkça kurgular kompleksleşmeye başlayacaktır. Bu yöntemde karşılaşılabilecek bir diğer zorluk ise araya girecek olan yeni adımların gelmesi durumudur.
Koordinatör tabanlı Saga (Coordinator based Saga): Sistemde var olan bir koordinatörün yürütülecek iş emirlerine karar vermesi ve bu emirleri gerekli alt sistemlere iletmesi mantığına dayalı bir yapıdır.
Mağaza örneğimiz üzerinden böyle bir sistem hayal etmeye çalışalım. Müşteri gelip bir sipariş bildirir. Sipariş bilgisini alan koordinatör depo görevlisine ürünü getirmesini söyler. Ürün geldiği zaman koordinatöre haber verilir. Koordinatör müşteriden ücretin alınması işleminin başlaması için gerekli iş emrini verir. Ücret alındı bilgisi geldikten sonra fatura kesilmesi emrini verir. Fatura kesme işleminde bir hata olduğunu hayal edelim. Koordinatöre bu bilgi iletilir ve koordinatör para iadesi yapılmasına karar verir. Para iadesinin yapıldığı bilgisi koordinatöre iletilince koordinatör ürünün depoya geri götürülmesi için emir verir. Süreç bu şekilde koordinatörün emirleri doğrultusunda gelişir.
Bu gibi yapılar çok adımlı işlemler için daha uygundur. Satış işlemine ait prosedür değiştiği zaman koordinatöre bu durumu bildirmek yeterli olacaktır. Yani sistem üzerinde değişiklik yapmak daha kolaydır. Bu yapının sorunlu tarafı koordinatörün çok fazla sorumluluğa sahip olmasıdır. Bazı durumlarda koordinatöre ait olmayan sorumluluklar da bu servise yazılabilmektedir ve sistem daha da komplike hale gelebilmektedir. Bu duruma özellikle dikkat edilmesi gerekmektedir. Ayrıca bu sistemlerin tek yıkım noktasının (single point of failure, spof) olması da bir dezavantajdır. Koordinatörün işe gelmediği gün yani koordinatör servisinizin kapandığı an tüm sistem durmaktadır.
Koordinatör tabanlı Saga yapısına örnek oluşturacak OrderManagement adındaki uygulamaya ait yazıya bu link aracılığı ile ulaşabilirsiniz. İyi günler dilerim :)