# Transaction Yönetimi

Bu bölümün başlığı için birkaç farklı seçenek arasından bir seçim yapmak durumunda kaldığımı itiraf edeyim. **Transaction Yönetimi**, **Transaction Bütünlüğü**, **Veri Tutarlılığı** (Data Consistency) vb. gibi kavramların aslında aynı kapıya çıktığını söyleyerek başlayabiliriz.

Konuyu aşağıdaki başlıklara bölerek anlatmayı uygun buldum;

* Transaction ve Transaction bütünlüğü nedir?
* ACID prensipler hakkında
* Monolith uygulamalarda transaction yönetimi
* Mikroservis Mimari'de transaction yönetimi
* Mikroservis Mimari’de transaction yönetimi için **Two-Phase Commit** ve **Saga** tasarım kalıpları
* **Two-Phase Commit** vs. **Saga**

## Transaction Kavramı

Transaction kelime anlamı olarak iş/işlem anlamına gelmekle birlikte kullanıldığı alana göre farklı anlamlar kazanabilmekte. Bankacılık sektöründe, yapılan bir EFT için kullanılırken, muhasebe dünyasında deftere yapılan her bir yazma işlemi için kullanılabilir. Veri tabanı üzerinde yapılan işlemlerin her birisi bizim için bir **transaction**’dır.

Bazı business transaction’lar, birden fazla transactionın çalışmasını gerektirebilir. Eğer Mikroservis Mimari söz konusuysa, bu aslında birden fazla servisin ard arda çalışması anlamına gelir. Bu arda arda çalışan transaction’lar dizisinin yönetilmeye ihtiyacı vardır. Yönetiminden kastımızın ne olduğuna bir örnek senaryo üzerinden bakalım.

Bir e-ticaret sitesinde bir ürünün siparişinden müşteriye teslim edilmesine kadar geçen sürede bir çok sürecin dolayısıyla transaction’ın işletildiğini biliyoruz.

Örneğin ödeme işlemi ve sonrasında ürünün stoktan düşülmesi süreçlerini ele alalım. Ödeme işlemi başarılı olmadan, stoktan düşme süreci ve sonraki süreçler işletilemez. Peki ödeme işlemi başarılı olduktan sonraki süreçlerin birisinde bir hata meydana gelirse ne yapmalıyız? Yazılım tarafında bu durumu nasıl yöneteceğiz? Bu hata oluştuktan sonra o ana kadar veri tabanı üzerinde yapılmış olan işlemlerin tümünü geri almak gibi bir sorunumuz var. İşte bu sorun ve çözümü **transaction bütünlüğü/tutarlılığı/yönetimi** konusunun temelini oluşturmakta.

## ACID Prensipler

ACID, değişikliklerin bir veri tabanına nasıl uygulanacağını yöneten 4 adet prensip sunar. Bunlar, **Atomicity**, **Consistency**, **Isolation** ve **Durability** prensipleridi&#x72;**.** Bir kaç cümle ile açıklamak gerekirse;

**Atomicity**: En kısa ifadesiyle ya hep, ya hiç. Arda arda çalışan transaction’lar için iki olası senaryo vardır. Ya tüm transaction’lar başarılı olmalı ya da bir tanesi bile başarısız olursa tümünün iptal edilmesi durumudur.

**Consistency**: Veri tabanındaki datalarımızın tutarlı olması gerekir. Eğer bir transaction geçersiz bir veri üreterek sonuçlanmışsa, veri tabanı veriyi en son güncel olan haline geri alır. Yani bir transaction, veri tabanını ancak bir geçerli durumdan bir diğer geçerli duruma güncelleyebilir.

**Isolation**: Transaction’ların güvenli ve bağımsız bir şekilde işletilmesi prensibidir. Bu prensip sıralamayla ilgilenmez.Bir transaction, henüz tamamlanmamış bir başka transaction’ın verisini okuyamaz.

**Durability**: Commit edilerek tamamlanmış transaction’ların verisinin kararlı, dayanıklı ve sürekliliği garanti edilmiş bir ortamda (sabit disk gibi) saklanmasıdır. Donanım arızası gibi beklenmedik durumlarda transaction log ve alınan backup’lar da prensibe bağlılık adına önem arz etmektedir.

## Monolith Uygulamalarda Transaction Yönetimi

Monolith mimaride transaction yönetimi Mikroservis Mimariye kıyasla oldukça kolaydır. Bir çok framework veya dil transaction yönetimi için kendi içlerinde bazı api’lar içerirler. (dotnet için **TransactionScope** class’ı gibi) Bu api’lar tüm uygulamanın tek bir veri tabanına sahip olduğu, dolayısıyla tüm transaction’ların tek bir context üzerinde çalıştığı senaryolar için geliştirilmişlerdir. Yani monolith mimarilerde bu api’lar ile basitçe **commit** ve **rollback** işlemlerini yapabiliyoruz.

**Commit** işlemi **scope**’a dahil edilen tüm transaction’lar başarıyla çalıştığında en son yapacağımız işlem iken, **rollback** ise scope’dak herhangi bir transaction’da bir hata oluşması durumunda tüm işlemi iptal etmek için kullanılır.

Transaction scope içerisinde işletilen transactionlar **commit** edilene kadar diske yazılmadan memory’de tutulurlar ve eğer herhangi bir **t** anında **Rollback** yapılırsa, scope içerisinde o ana kadar işletilmiş tüm transactionlar memory’den silinerek işlem iptal edilmiş olur. **Rollback** yapmadan, **Commit** edildiğinde ise diske (veri tabanına) yazılarak transaction başarıyla tamamlanmış olur.

Aşağıdaki ekran alıntısında **TransactionScope** kullanım örneğini görebilirsiniz. Burada söz konusu transaction 2 step’li bir transaction’dır. Örneğin ikinci transaction’da meydana gelecek bir hata **Complete** metodunun çalışmamasına, dolayısıyla birinci işlemin de diske yazılmamasıyla yani iptaliyle sonuçlanacaktır.

<figure><img src="https://1041106579-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FctwsWVHhTx4xf0lm1noh%2Fuploads%2F0rhOjLD76gAb6OJMM80B%2F002.png?alt=media&#x26;token=1543501f-8b9a-4a80-bb35-66149eb9de98" alt=""><figcaption></figcaption></figure>

## Mikroservis Mimari’de Transaction Yönetimi

Mikroservis Mimari'ler dağıtık mimarilerdir ve dağıtık mimaride bir çok konu için tek ve kolay bir çözüm genelde yoktur. Kimlik doğrulamadan, loglamaya, caching’den integration test yazmaya kadar bir çok konu uzmanlık gerektiren zor konular olarak karşımıza çıkmakta. Transaction yönetimi konusunu da bu konulara dahil ettiğimizi söylememe gerek yok sanırım.

Mikroservis Mimari’lerde yukarıda bahsettiğim ACID prensiplerini korumak kolay bir iş değildir ve birden fazla yol mevcuttur diyebiliriz. Burada yalnızca **2PC** ve **Saga** konularına değineceğiz.

### Two-Phase Commit (2PC) <a href="#id-5d6d" id="id-5d6d"></a>

2PC, dağıtık mimarilerde yukarıda bahsettiğimiz ACID prensipleri korumaya imkan sağlayan bir protokoldür. İsminden de anlaşılacağı üzere 2 fazdan oluşmaktadır ve bu 2 fazı yöneten bir **Kordinatör**’ ümüz mevcuttur. İlk faz **prepare** (hazırlık veya oylama (voting)olarak da geçer, ikinci faz ise **commit** fazı olarak adlandırılır.

Yine e-ticaret örneği üzerinden açıklayalım. Ödeme ve stoktan düşme transaction’larını ele almıştık. Akış başladıktan ve transaction’lar tamamlandıktan sonra **Kordinatör,** hazırlık fazında bu 2 transaction’ın başarılı olup olmadığını yani **commit** işlemi için hazır olup olmadıklarını sorar. Eğer her iki transaction’dan commit için hazırız yanıtını alırsa 2. yani **commit** fazını icra ederek, işlemlerin kalıcı olarak diske yazılmasını sağlar.

Hata senaryosuna gelecek olursak; **Kordinatör**, birinci faz sonunda transaction’lardan **birisinden bile** commit edilemez yani hata oluştu bilgisini alırsa mevcut tüm transaction’ları iptal eder. Böylece işlem bir bütün olarak iptal edilmiş olur.

<figure><img src="https://1041106579-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FctwsWVHhTx4xf0lm1noh%2Fuploads%2FpND9nw9kbHhhqCsTkF9f%2F003.png?alt=media&#x26;token=af53cd70-803b-4d77-8e57-708c43cc6a32" alt=""><figcaption></figcaption></figure>

### Saga Pattern <a href="#b497" id="b497"></a>

Dağıtık mimarilerde transaction yönetimi için kullanılan yöntemlerden birisi olan **Saga Pattern,** ilk olarak 1987 yılında [**akademik bir makalede**](https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf) ortaya atıldı.

**Saga**, her transaction’ın farklı ve bağımsız bir servis üzerinde lokal olarak çalıştığı ve yine o servis içerisinde verisini güncellediği transaction’lar dizisidir. Bu tasarım kalıbına göre, ilk transaction, dış bir etki ile (kullanıcının kaydet butonuna tıklaması gibi ) tetiklenir ve artık sonraki tüm transaction’lar bir önceki transaction’ın başarılı olması durumunda tetiklenecektir. Transaction’lardan herhangi birisinde meydana gelecek bir hata durumunda ise tüm süreç iptal edilerek **Atomicity** presibine bağlılık sağlanmış olur. Biraz havada kalmış olabilir ancak aşağıdaki örnek senaryo çizimlerimle biraz daha net anlaşılacağını düşünüyorum.

Saga’yı uygulamak için bir kaç farklı yöntem mevcuttur. Ben **Events/Choreography** metodu ile Saga’yı servislerimiz arasında nasıl implemente edeceğimizden bahsedeceğim. Dilerseniz diğer bilindik yöntem olan **Command/Orchestration** metodunu da araştırabilirsiniz.

### **Events/Choreography Yöntemiyle Saga Uygulaması** <a href="#id-7ad5" id="id-7ad5"></a>

Bu yöntemde ilk servis işini icra ettikten sonra bir **event** fırlatır ve bu event’ı dinleyen servis veya servisler tetiklenerek kendi local transaction’larını çalıştırır. Yani her servis aslında bir önceki servisin “ben işimi hallettim sıra sende” demesini bekler. Son servis çalıştıktan sonra artık bir event fırlatmaz ve süreç sonlanır.

**Events/Choreography** yöntem&#x69;**,** Saga’nın prensiplerine en uygun olan yöntemdir. Uygulaması ve yönetmesi nispeten daha  kolaydır. Transaction’lar birbirlerinden tamamen izoledir ve birbirleri hakkında bilgi sahibi olmak zorunda değildirler. Ancak bu yöntemde servislerimizin ve dolayısıyla event’lerimizin sayısı arttıkça sistemin karmaşıklığının da artacağını ve yönetilmesi zor bir hal alabileceğini unutmamak gerekiyo&#x72;**.**

Yine sipariş verme örneğimiz üzerinden başarılı ve başarısız sipariş işlemlerini, iki farklı akış diyagramı üzerinden inceleyelim.

**Örnek Başarılı İşlem Senaryosu (Commit)**

Senaryomuz gayet basit. Hatırlarsanız ilk transaction’ımız harici bir müdahale ile tetikleniyordu. Kullanıcının ekrandan **Satın Al** butonuna tıklamasıyla;

* Saga’mızın ilk transaction’ı yani **Sipariş Servisi** tetiklenir.
* Sipariş servisi **Sipariş Oluşturuldu** Event’ini fırlatır.
* Bu event’i dinleyen **Ödeme Servisi** tetiklenir.
* Ödeme servisi **Ödeme Alındı** Event’i fırlatılır.
* Bu event’i dinleyen **Stok Servisi** tetiklenir.
* Stok servisi **Stoktan Düşüldü** Event’ini fırlatır.
* Bu event’i dinleyen **Bildirim** **ve Sipariş Servisleri** tetiklenir.
* Bildirim servisi kullanıcıya e-mail/sms gönderir.
* Sipariş Servisi siparişin durumunu **Başarıyla Tamamlandı** durumuna günceller.

<figure><img src="https://1041106579-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FctwsWVHhTx4xf0lm1noh%2Fuploads%2FbHhqlXQcsf6Bg0Gg9E41%2F004.png?alt=media&#x26;token=fc06e868-5fd7-4231-adf0-1e6dcc36b583" alt=""><figcaption></figcaption></figure>

**Örnek Başarısız İşlem Senaryosu (Rollback)**

Kullanıcının ekrandan **Satın Al** butonuna tıklamasıyla;

* Saga’mızın ilk transaction’ı yani **Sipariş Servisi** tetiklenir.
* Sipariş servisi **Sipariş Oluşturuldu** Event’ini fırlatır.
* Bu event’i dinleyen **Ödeme Servisi** tetiklenir.
* Ödeme servisi **Ödeme Alındı** Event’i fırlatılır.
* Bu event’i dinleyen **Stok Servisi** tetiklenir.
* Stok servisi **Ürün Stokta Yok Hata** Event’ini fırlatır.
* Bu event’i dinleyen **Ödeme ve Sipariş Servisleri** tetiklenir.
* Ödeme servisi kullanıcıya para iadesi yapar.
* Sipariş Servisi siparişin durumunu **Başarısız** durumuna günceller.

<figure><img src="https://1041106579-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FctwsWVHhTx4xf0lm1noh%2Fuploads%2FKLQbatHeXC0ICRcgjqqa%2F005.png?alt=media&#x26;token=54318a46-79fd-48c3-a43b-322016e33621" alt=""><figcaption></figcaption></figure>

**Diyagramları Yorumlayalım**

Öncelikle **Event Fırlatma** ifadesine aşina olmayanlarınız için biraz hava da kalmış olabileceğinden buna değinelim. Diyagramlarda berlittiğim Event’lerin her birisi aslında veri tabanına veya bir message queue’ya atılan bir kayıttan ibaret. Burada veri tabanı mı yoksa mesaj kuyruğu mu olacağı sizin tasarımınıza kalmış. Eğer ciddi yük altında çalışan bir uygulama söz konusuysa RabbitMQ, MsMQ veya Kafka gibi mesaj kuyruk yapıları kullanmanızı öneririm.

Gelelim **Event Dinleme** olayın&#x61;**.** RabbitMQ Message Queue kullanılan bir mimaride açıklayacak olursak. Oluşan her event bir kuyruğa yazılır. Ödeme servisi **Sipariş Oluştu** event’ini dinliyor demek, bu event’in yazıldığı kuyruğa atılan her yeni mesajın Ödeme Servisi tarafından **Subscribe** edilmesi yani tüketilmesi demektir. Her servis için ayrı bir kuyruk olduğunu hayal edin. Hangi event’in hangi kuyruk veya kuyruklara yazılacağını RabbitMq’nun gelişmiş routing yapısıyla kolayca yönetebiliyorsunuz. Başarılı ve başarısız senaryo diagramlarını açıklayalım biraz daha net anlaşılacaktır.

Başarılı senaryo için çok fazla söylenecek bir şey yok aslında. Yalnızca **Stoktan Düşüldü** event’ine dikkatinizi çekmek isterim. Dikkat ederseniz bu event’i dinleyen iki servis var. Sipariş servisi ve bildirim servisimiz. Birkaç cümle önce ‘**kuyruk veya kuyruklara’** ifadesini kullanmıştık yani biz RabbitMQ’ya bir mesaj gönderirken bu mesajı birden fazla kuyruğa yaz diyebiliyoruz.

Gelelim başarısız senaryoya;

Stok servisine kadar her şey yolundaydı, ancak kullanıcının ekranında **Stokta Var** olarak gördüğü ürünün aslında stokta mevcut olmadığı ortaya çıktı ve neticede stok servisi **Stoktan Düşme Hata** event’ini fırlattı. Yani **hata durumunda bir önceki servis için event fırlatılır** diyebiliriz.

Tabi olay bu kadar da basit değil. Örneğin, ödeme servisinin hata alması durumunda sadece ilk servis için event oluşturulması yeterli olacakken, bildirim servisi için farklı bir tasarım yapmak gerekebilir. Neticede bildirim servisi **Saga** akışını bozan, işlemi sekteye uğratan bir servis değil. Yani oluşacak bir hatanın başka bir servis tarafından dinlenmesi gerekmeyebilir. Bildirim servisinde oluşacak hatalar için **retry policy’**&#x6C;er tanımlayabiliriz.

### Saga mı? 2PC mi? <a href="#a770" id="a770"></a>

Açıkçası ben her durumda Saga’yı uygulamayı tercih ederdim. Bunun en önemli nedeni, 2PC nin performans noktasında dezavantaj sağlaması. Hatırlayın, tüm katılımcılardan yanıt gelene kadar kaynakların lock'lı durumda bekletilmesi durumu. Ek olarak, 2PC ile gerçekleştireceğiniz her senaryoyu Saga ile de yapabilirken, tersi her zaman mümkün olmayabilir.

Saga’yı **Long Running Transaction** dediğimiz, transaction’ı birbirinden bağımsız ve asenkron çalışabilen step’lere bölerek yönetmenin doğru olduğu senaryolarda kullanırken, **2PC** nisbeten daha hızlı sonlanan ani transaction’lar için tercih edilebilir.

Saga’da bir sipariş işlemini step’lere (**sipariş-ödeme-stok-email vs..**) bölerek yönetmek kolaylık sağlıyor. Bunun yanında Saga’da her step’ten sonra commit işlemi yapıldığından, yani sonuç dış dünyaya gerçek olarak yansıdığından dolayı (**ödeme adımında müşteriden ödeme alınması gibi**) rollback işlemi daha kritik bir hal alıyor. Ödeme işleminin rollback yapılması yani müşteriye para iadesi yapılması esnasında oluşabilecek bir hata durumunda nasıl aksiyon almamız gerekir? Bu konu belki de Saga’nın en kritik konusu olabilir.
