Yazılım Mimarileri ve Tasarım Desenleri Üzerine

Microservice Mimarilerde Saga Pattern İle Transaction Yönetimi

Merhaba,

Biliyorsunuz ki, yazılımsal operasyonlarda belirli senaryolarla baş edebilmek için tecrübevi birikim olarak yazılımcılar tarafından tatiksel tanımlanmış design pattern stratejilerinden istifade etmekteyiz. Design pattern uyarlılığı microservice yapılanmalarında da varlığını göstermekte ve yaşanılan problemlerde çözüm olarak imdadımıza yetişmektedir. Bu içeriğimizde, microservice yapılanmalarında en kritik konulardan birisi olan distributed transaction senaryolarında servisler arası veri tutarlılığını sağlamamıza imkan veren Saga Pattern üzerine konuşuyor olacağız. Tabi makale seyri boyunca acele etmeyecek ve Saga Pattern nedir? Ne şekilde uygulanmaktadır? gibi soruları cevaplandırmaya başlamadan önce pergeli doğru noktaya koyarak transaction kavramı üzerinden sınırlarımızı çizmeye başlayacağız. Ardından distributed transaction kavramını anlayabilmek için öncelikle servis başına veritabanı prensibi üzerinde istişarede bulunacağız. Devamında ise verisel açıdan yapılan değişikliklerin veritabanları üzerinde hangi prensipler doğrultusunda uygulanacağını(ACID) açtıktan sonra artık neden Saga Pattern’a ihtiyacımız olduğunu netleştirmiş olacağız. Nihayetinde asıl konumuz olan Saga Pattern’ın detaylarına girecek ve kavramsal olarak nelerden oluştuğunu inceledikten sonra teknik açıdan anotomisini inceleyeceğiz. Yani anlayacağınız, uzun ama bir o kadar da öğretici bir içerik bizi bekliyor olacak 🙂 Eee o zaman hiç vakit kaybetmeden buyrun başlayalım…

Bu Makalenin Konusu Nedir?

Her şeyden önce bu içerikte neyin anlatılmaya çalışıldığını anlayarak başlayalım. Biliyorsunuz ki, microservice mimarilerinde ideal olarak her bir servisin kendine ait bir veritabanı olması gerekmektedir.(Eğer bilmiyorsanız artık öğrenmiş oldunuz 🙂 Gerçi aceleci davranmaya gerek yok. Bu ideal kararı birazdan ayrı bir başlık altında eğrisi ve doğrusuyla değerlendiriyor olacağız.) İşte bu birden fazla servise dağıtılmış veritabanı sistemi distributed transaction olarak nitelendirilmektedir. (Keza bu kavramı da ayrı başlık altında daha da detaylandırıyor olacağız.) Her servisin kendine ait bir veritabanı olması dolayısıyla bu veritabanların arasında yapılan işe/hizmete göre bütünsel bir veri tutarlılığının sağlanmasını gerektirmektedir. Ee haliyle bu durumda gayet doğaldır, öyle değil mi? Yani bir yazılım projesinde birden fazla veritabanı kullanılıyorsa eğer bu veritabanlarındaki verilerin bütünsel açıdan tutarlı olmasını beklemek işin doğasında vardır.

Bu durumu şöyle bir örnekle zihinlerimizde tahayyül edelim; farz edelim ki bir e-ticaret yazılımı geliştiriyoruz. Bu yazılımın sipariş alma işlemlerini ‘Order’, siparişten sonra stok işlemlerini ‘Stock’ ve son olarak da ödeme işlemlerini ‘Payment’ servislerinde yaptığımızı düşünelim. Tabi ki de tüm bu servisin yukarı paragraflarda hafiften çıtlattığımız gibi kendilerine ait veritabanları olduğunu düşünelim ve senaryotik açıdan işlemlerin şu şekilde seyr ettiğini farz edelim :

Kullanıcı herhangi bir ürüne dair sipariş verdiğinde ‘Order’ servisi bu işlemi gerçekleştirecek ve kendi veritabanına ekleyecektir. ‘Payment’ servisi ise ödemeyi sağlayacak ve ödeme başarılıysa ‘Stock’ servisinde ki ilgili ürüne karşılık gelen stok miktarını düşürecektir.

Şimdi bu işlem seyrinde, ‘Order’ servisi gelen siparişi oluşturduktan sonra ‘Payment’ servisi o siparişe dair ödemeyi başarılı gerçekleştirirse eğer ‘Stock’ servisinde siparişteki ilgili ürüne dair stok adedinin düşürülmesi gerekmektedir. Aksi taktirde bir tutarsızlık meydana gelecektir. Düşünsenize, sipariş verilip ödeme yapıldıktan sonra hala stok miktarı aynı kalmaktadır. Bu durumun yazılımdaki verisel istatistiklere ve o yazılımı kullanan işletmeye verdiği zararı düşünün!

İşte bizim birden fazla serviste birden fazla veritabanıyla çalışılması(distributed transaction) durumlarında veri tutarlılığından kastımız budur. Haliyle bu içeriğimizde distributed transaction senaryolarında bu bahsedilen veri tutarlılığının sağlanabilmesi için önerilen çözümlerden biri olan Saga pattern’ınını inceliyor olacağız.

Transaction, Distributed Transaction ve Compensable Transactions Nedir?

Evet, şimdi teorik olarak bu makaleyi daha da iyi anlayabilmek için gerekli olan temel kavramları inceleyebiliriz. İlk olarak transaction kavramına değinerek başlayacağız.

Microservice Yapılanmasında Veritabanları Nasıl Tasarlanmalıdır?

Şimdi sıra microservice yapılanmalarında tasarımsal açıdan veritabanlarını nasıl yapılandıracağımızı konuşmaya geldi. Bu sorunun cevabını doğru şekilde verebilmek Saga pattern’ının temel gereksiniminin anlaşılmasında yardımcı olacağı kanaatinde olduğumdan dolayı gelin detaylıca istişare eyleyelim.

Microservice yaklaşımında her bir servisin bağımsız olarak geliştirilebilmesi, deploy edilebilmesi ve ölçeklendirilebilmesi için kendine ait, özel ve direkt erişilebilir veritabanlarına ihtiyacı vardır. Tabi ki de birden fazla servis aynı veritabanını kullanabilir ama bu durum pek ideal değildir. Servisler kendilerine ait veritabanlarına sahip olarak esasında kendi aralarında gevşek bir bağımlılık sergileyebilme özelliği kazanabilmektedirler. Böylece her servis ihtiyacına uygun veritabanı seçebilme hüvviyetine sahip olacaktır. Örneğin; metin araması yapan bir servis Elasticsearch’ü kullanırken, bu metinlerle ilgili farklı bir işlev yapan başka bir servis NoSQL yaklaşımı olan MongoDB’yi kullanabilir.

Her bir servisin kendi veritabanını kullanmasının elbette ki dezavantajları da mevcuttur. Bunlardan en önemlisi diyebileceğimiz, veritabanları arasında verileri birleştirerek(join) sorgulamak ve bütünsel istatiksel sonuçlar üretebilmek oldukça zor ve zahmetlidir bir işlevdir.

Değişiklikler Veritabanlarına Nasıl Uygulanmalıdır?
(ACID Prensipleri)

Veritabanlarının nasıl tasarlanacağından sonra dağıtılmış veritabanlarında yapılan verisel değişikliklerin nasıl uygulanacağını da konuşmakta fayda vardır. Muhtemelen bu başlığı gördüğünüzde la hoca veritabanlarındaki değişikliği uygulamakta ne var la allahisen! alt tarafı insert, update, delete işte! diyenleriniz olacaktır. Haliyle işi gücü bırakıp gelip size bu değişikliklerin teknik olarak nasıl uygulanması gerektiğini anlatma gibi gereksiz bir vizyona sahip olduğumu düşünmenizi istemem 🙂 Burada veritabanlarına yapılacak verisel değişiklerin hangi “prensipler” çerçevesinde uygulanması gerektiği üzerine istişare eyliyor olacağız. Yani konumuz ilkeler 😉

Bir yazılımın dağıtılmış veritabanları üzerinde bulunduğu verisel herhangi bir müdahale neticesinde bu işlemin nasıl uygulanacağını belli başlı prensipler doğrultusunda değerlendirmekteyiz. Bu prensipler sayesinde veritabanları üzerinde veri tutarlılığına dair tatminkarlık seviyemizi ölçebilir ve çıkılan yolda aklın ne kadar ütüde kalması gerektiğine dair bir nebze karar verebiliriz.

Dağıtılmış veritabanlarında yapılan değişiklerin nasıl uygulanacağını ifade eden prensipler baş harflerinden meydana gelen ACID Prensipleri olarak nitelendirilmektedir. ACID Prensipleri; Atomicity, Consistency, Isolation ve Durability olmak üzere dört tanedir.

Saga Nedir?

Saga, farklı ve bağımsız servisler üzerinde birden fazla transaction’ın sistematik bir şekilde işlenerek veri tutarlılığının sağlanmasını ifade eden ve ilk olarak 1987 yılında akademik bir makalede(bknz : SAGAS) yer edinen bir design pattern’dır. Bu pattern’ın işlevsel mantığı ilk transaction dış bir etki ile(örneğin kullanıcı tarafından butona tıklama) tetiklendikten sonra, bir önceki servisteki transaction’ın başarılı olması durumuna göre bir sonraki servisteki transaction’ın tetiklenmesi ve bir silsile halinde bu şekilde diğer servisler üzerinde tetiklenmenin devam etmesi üzerinedir. Böylece geliştirilen proje her ne kadar microservice yaklaşımını benimsemiş olsa da yapılan bir operasyon neticesinde tüm servislerdeki datalar tutarlı bir şekilde işlenmiş olacaktır.

Tabi süreçte transaction’lardan herhangi birinde oluşabilecek bir hata veya iş mantığı gereği iptal edilmesi gereken bir adım söz konusu olursa tüm süreç iptal edilecek ve işlenen transaction’lar tüm servislerde geri alınacaktır(Compensable Transaction) Böylece Atomicity prensibi desteklenmiş olacaktır.

Saga, microservice’ler arasında doğru transaction yönetimi ve veri tutarlılığı sağlayan bir design pattern’dır.

Saga’nın uygulanması için teknik olarak Events/Choreography ve Command/Orchestration isminde iki farklı implemantasyon geliştirilmiştir. Şimdi gelin bu implemantasyonları teorik olarak detaylıca inceleyelim.

Saga – Events/Choreography Implemantasyonu

Bu implemantasyonda microservice’ler arası merkezi bir denetim ve haberleşme noktası olmaksızın birbirleriyle eventler aracılığıyla haberleşilmesi esastır. Yani servisler arası iletişimin asenkron olarak tasarlanması gerektiğini savunmaktadır.

Genellikle bu iletişimin message broker ile gerçekleştirilmesi tercih edilir. Ama bu senkron bir iletişiminde kesin olamayacağı anlamına gelmemekte sadece ideal olarak eventler sayesinde asenkron tercih edilmektedir.

Taktiksel olarak Choreography’u incelersek eğer, ilk serviste başlayan transaction işlevini bitirdikten sonra sonraki servise haber gönderebilmek için message broker üzerinden bir event fırlatacaktır. Ardından bu servisteki transaction işlevini bitirdikten sonra kendisinden sonraki servisi tetikleyebilmek için yine bir event fırlatacaktır. Ve bu süreç client’ın istediği işlem bitene kadar silsile halinde işlevdeki son servise kadar devam edecektir. Buradan anlayacağımız her bir servisin kendisinden sonraki servisin tetiklenip tetiklenmeyeceğine dair kararı verdiğini görmekteyiz. Her bir servis yapılan işlev neticesinde kendi sürecine bağlı bir şekilde başarılı ya da başarısız bir karar vermekte ve bu neticeye göre ya kendisinden sonraki servisteki transaction’ın başlamasını sağlayabilmekte ya da tüm transaction’ları geri alabilmektedir. Yani her bir servis bizzat karar verici konumundadır.

Choreography implemantasyonu, distributed transaction’a katılacak olan microservice sayısının 2 ile 4 arasında olduğu durumlarda tercih edilir. 4’ten fazla servis durumunda yazımızın devamında inceleyeceğimiz Orchestration implemantasyonunu uygulamak daha uygundur..

Choreography’de her bir servis kuyruğu dinler. Dinlediği event message türüne göre gelen bir message söz konusuysa gerekli işlemlerini gerçekleştirir ve sonuç olarak muhakkak durumu bildiren başarılı yahut başarısız bilgisini kuyruğa event olarak ekler. Ardından diğer servisler bu event’e göre ya işlevlerine devam edecektirler ya da tüm transaction’lar geri alınıp veri tutarlılığını sağlayacaktırlar.

Örnek olarak aşağıdaki görseli incelersek eğer bir e-ticaret uygulamasında Choreography implemantasyonuyla oluşturulan siparişi görmekteyiz.Bu uygulamada bir siparişi oluşturabilmek için;

  1. Order serviste alınan POST isteği neticesinde sipariş oluşturulur.
  2. Order events channel kuyruğuna ‘OrderCreated’ misali bir event gönderilir.
  3. Customer servisi ise Order events channel‘da ki ‘OrderCreated’ event’ına subscribe olmaktadır. Haliyle ilgili kuyruğa beklenen türde bir event gelirse Customer servis tetiklenecektir.
  4. Customer servis müşteriye dair gerekli tüm işlemleri yaptıktan sonra eğer işlemler başarılıysa ‘OrderSucceded’ isminde yok eğer değilse ‘CreditLimitExceeded’ isminde Customer evens channel kuyruğuna bir event yayar.
  5. Haliyle ilgili kuyruğu dinleyen(subscribe) Order servis gelen event’ın türüne göre ya siparişi onaylar ya da reddeder.

Görüldüğü üzere servisler arası uçtan uca(point to point) bir iletişim olmadığı için coupling azalacaktır. Ayrıca transaction yönetimi merkezi olmadığı için performance bottleneck azalacaktır..

Ayrıca Choreography yöntemi, sorumlulukları Saga katılımcı servisleri arasında dağıttığından dolayı tek bir hata noktası olmayacaktır. Bundan dolayı da ekstra bakım gerekmemektedir.

Şimdi Choreography implemantasyonuna daha efektif bir örnek vererek ilerleyelim. Örneğimiz yine bir e-ticaret yazılımının sipariş sürecindeki detaylarını yansıtacaktır;

Yukarıdaki adımlardan herhangi bir durumda hata meydana geldiği taktirde tüm işlemlerin geri alınması gerekmektedir. İşte bu durumda aşağıdaki senaryo devreye girecektir;

Distributed transaction süreçlerinde bir işlemi geri almak demek esasında o işlemi telafi etmek ya da tam tersini uygulamak için başka bir işlem yapılması demektir(Compensable Transaction) Dolayısıyla yandaki görselden yola çıkarak üstteki işlem akışının yetersiz stok miktarından dolayı Stock Service‘de başarısızlığa uğradığını varsayarak sürecin nasıl işlediğini simüle edelim…

Saga – Events/Choreography Implemantasyonunun Dezavantajları Nelerdir?
Saga – Command/Orchestration Implemantasyonu

Bu yaklaşımda servisler arası distributed transaction merkezi bir denetleyici ile koordine edilir. Bu denetleyiciye Saga State Machine ya da bir başka isim olarak Saga Orchestrator denmektedir. Saga Orchestrator, servisler arasındaki tüm işlemleri yönetir ve olaylara göre hangi işlemin gerçekleştirileceğini söyler.

Saga Orchestrator, diğer ismi olan Saga State Machine adından da anlaşıldığı üzere her kullanıcıdan gelen isteğe dair uygulama state’ini(durum) tutmakta, yorumlamakta ve gerektiğinde telafi edici işlemleri uygulamaktadır.
Misal olarak yanda şematize edilmiş e-ticaret senaryosunu ele alırsak eğer;

Saga Orchestration implemantasyonunda Saga Choreography’de olduğu gibi asynchronous messaging pattern tercih edilmektedir.

Yukarıdaki senaryoda servislerden herhangi birinde olası bir hata meydana geldiği taktirde aşağıdaki gibi hareket edilmelidir. Bu örnekte hata ‘Stock Service’ üzerinden misallendirilmektedir;

Her işlem için Saga üzerinde state bilgisi tutmak hangi adımda süreci yanlış yönettiğinizi görmeyi kolaylaştıracaktır.

Orchestration implemantasyonunun faydalarına gelirsek eğer;

Orchestration implemantasyonunun tek dezavantajı tüm iş akışının yönetiminin Saga Orchestrator tarafından gerçekleştiriliyor olmasıdır diyebiliriz.

Nihai Olarak Saga

Artık içeriğimizi noktalamadan önce Saga’ya dair nihai olarak yorumlarımızı yaparak genel mahiyette fikrimizi ortaya koyalım..

İşte bu ve bunlara benzer durumlardan dolayı Saga pattern’ının kullanımı net özen gerektirmektedir.

Saga Orchestrator, subscribe olan tüm servisleri çağırabilir ancak servisler orchestrator’ü çağıramaz, çağırmamalıdır!

Evet.. Konuyu teorik olarak incelediğimiz bir içeriğimizin daha sonuna gelmiş bulunuyoruz. Bundan sonraki içeriklerimizde bu teorinin üzerine sırasıyla choreography ve orchestration implemantasyonlarına dair pratiksel detaylar barındıran esas operasyonel içeriklerimizi klavyeye alıyor olacağız. Dolayısıyla sizler bu makaleyi okurken ben deniz de sonraki makalelerin altyapılarını atmaya gidiyor, kolaylıklar diliyorum 🙂

Okuduğunuz ve eşlik ettiğiniz için teşekkür ederim…

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Kaynakça

https://microservices.io/patterns/data/saga.html
https://medium.com/devopsturkiye/microservice-mimarilerde-transaction-y%C3%B6netimi-nas%C4%B1l-yap%C4%B1l%C4%B1r-228317e248ed
https://docs.microsoft.com/tr-tr/azure/architecture/reference-architectures/saga/saga
https://sefikcankanber.medium.com/saga-pattern-nedir-e4a447bef361
Mobil sürümden çık