Koordinatör tabanlı Saga Tasarımı | Sipariş Yönetimi
Koordinatör tabanlı Saga örneği oluşturacağız ve bu şekilde distributed transaction sorununa ait bir çözüm oluşturmuş olacağız. GitHub linki aracılığı ile örnek uygulamanın kodlarına erişebilirsiniz. Dağıtık mimarilerde işlem yönetimi (distributed transaction) ile ilgili yazıma da bu yazıya başlamadan önce göz atmanızın faydası olacaktır.
Kullanılan Araç ve Kütüphaneler
- Stateless: Durum geçiş izinlerini, durum değişikliklerinde yürütülecek aksiyonları sisteme tanıtmak için kullandığımız açık kaynak kodlu bir kütüphanedir.
- MassTransit: Tasarımımızda koordinatör, emirlerini yürütecek bileşenlerle doğrudan iletişim kurmayacaktır. Bunun yerine RabbitMQ aracılığı ile emirlerini sisteme bildirecektir. MassTransit kütüphanesi aracılığı ile göndereceğimiz emirlerin RabbitMQ platformuna erişmesini sağlayacaktır.
- EntityFramework: Verilen siparişlerin kayıtlarını saklamak için veri tabanı ile olan iletişimimizi kurmak için kullandığımız ORM (Object Relational Mapping, Nesne-İlişkisel Eşleme) aracıdır.
Giriş
Örnek olarak satış işlemine ait temsili bir süreç oluşturacağız. Örneğimize ait durum şeması aşağıdaki gibidir.
Eğer süreç hatasız ilerlerse, örneğimize ait akış aşağıdaki gibi olacaktır.
Siparişin teslim edilemediği duruma ait örnek akış ise aşağıdaki gibi olacaktır.
Gerçekleme
Tasarımı hayata geçirirken OrderStateMachine, OrderStateMachineFactory ve OrderStateOrchestrator adındaki sınıflardan faydalanacağız. Konuya ait önemli işlemler burada yapılacaktır. Bu sebeple de adı geçen 3 sınıfı inceliyor olacağız.
OrderStateOrchestrator
RabbitMQ üzerinden gelen mesajları işleyen ve dış sistemlere gerekli istekleri ileten sınıfımızdır. Örneğin TakePaymentCommand tipinden bir mesaj aldığı zaman ödeme sistemi ile iletişime geçecektir ve ödeme işlemi bu şekilde başlatılmış olacaktır. PaymentCreatedEvent tipinden mesajları izleyerek de siparişin “ödeme işlemi başladı” gibi bir statüye geçmesi için gerekli aksiyonları alacaktır.
OrderStateMachine üzerinde işlem yapmadan önce sipariş adına bir kilit alıyoruz. Bunun sebebi OrderStateMachine üzerinde yapılacak olan durum geçişlerinin tek tek incelenmesini istememizdir. Durum geçişleri sırasında yarış durumuna sebebiyet vermemek için sipariş üzerinde aynı anda sadece bir operasyona izin vereceğiz.
OrderStateMachineFactory
Bu sınıf aracılığı ile önce yeni bir sipariş oluşturarak veya var olan bir sipariş üzerinden sipariş durum makinası oluşturabilmekteyiz.
Eğer siparişe ait Id değeri sağlanırsa, sistemde var olan bir sipariş için durum geçiş makinası oluşturulmaktadır. Eğer satın alan, ödenecek tutar gibi bilgiler sağlanırsa da yeni bir sipariş oluşturulur. Yeni oluşturulan sipariş için durum geçiş makinası istemciye bildirilir.
OrderStateMachine
Siparişlerin durum geçişlerinin yönetildiği bileşenimizdir. Durum geçişi yapılması halinde hangi aksiyonların tetikleneceği de bu bileşen tarafından yönetilmektedir. Koordinatör tabanlı Saga örneğimizin koordinatörü bu sınıftır. Siparişe ait bilgilere ve siparişin o anki durumuna erişebilmekteyiz. Bu verilere erişmenin dışında da arayüzde görülen aksiyonlar aracılığı ile durum geçişleri talep edilebilir.
Not: Tetikle (trigger) gibi bir metot ile yürütülmek istenen aksiyonun bir enum karşılığı alınabilirdi. Bu durumda parametre geçilmek istendiği zaman bazı sorunlarla karşılaşabilirdik. Bu sebeple ortak bir tetikleme metodu yerine yürütülecek her bir aksiyon için ayrı metot oluşturma yoluna gittim.
StateMachine<OrderStates, OrderActions> objesi aracılığı ile sipariş üzerinde yürütülebilecek aksiyonlar ve bu aksiyonlara karşılık siparişin bulunabileceği statüler arasındaki ilişkileri kurgulayacağız.
Triggers kısmında durum makinası üzerinde parametre ile yürütülebilecek aksiyonları tanıtıyoruz. Örneğin; _changePaymentStatusTrigger adı ile tanımladığımız objeyi kullanırken PaymentStatuses tipinden bir enum değerini parametre olarak kullanmamız gerektiğini bildirdik. _changePaymentStatusTrigger objesinin ilkleme (initialization) işlemi sırasında bu tetikleme objesinin hangi aksiyona ait olduğunu da belirtiyoruz.
InterfaceSection kısmında durum geçiş talepleri için arayüzümüzde tanımladığımız metotlarımızın implementasyonları yer almaktadır. İlk örneğimizde siparişe ait ödeme işleminin başladığına dair duruma geçiş için çağrı yapılmıştır. Bir parametreye ihtiyaç duyulmamaktadır. Bu sebeple durum makinamızda yer alan Fire metodu kullanılarak yürütülmek istenen aksiyon bildirilmiştir. İkinci örneğimizde ise yukarıda bahsi geçen Trigger tipindeki objemizden faydalandık. Bu şekilde bir aksiyonun yürütülmesi için talepte bulunurken bir yandan da bu aksiyon için gereken parametreleri sağlamış olduk.
OnStateChanged kısmında durum geçişlerinde yürütülecek aksiyonları belirlemekteyiz. Bunun örneği olarak siparişin başlatıldığı (Submit) durumda ödeme alma emrinin verilmesi gibi aksiyonları sisteme tanıtmaktayız. Bir diğer örnekte de durum geçişi sırasında parametre kullanımını görmekteyiz.
Durum geçiş makinamızın etrafında yer alan bileşenleri inceledik. Artık durum makinamızın kurulumu kısmına gelebiliriz. Kurallar belirli olduğu için yapıcı metot (constructor) içerisinde durum makinamızı oluşturacağız.
_orderStateMachine=new StateMachine<OrderStates, OrderActions>((OrderStates) _orderModel.OrderState);
satırı aracılığı ile OrderStates enum tipinden durumları saklayacak ve bu durumlar arasındaki geçişleri OrderActions adındaki enumlar ile tetikleyecek makinayı kuruyoruz. Makinanın o anki durumu içinde siparişimizin durumunu kullanıyoruz. Makinayı oluşturduktan sonra Configure metodu ile siparişin durumu ile ilgili ayarlamalara başlıyoruz. OnEntry metodu ile bu duruma geçiş yapıldığı zaman yürütülecek aksiyonları çağırıyoruz. Permit metodu ile ayarlarını yaptığımız durumdayken hangi aksiyonun çağıralabileceği ve bu aksiyondan sonra hangi duruma geçiş yapılacağını tanımlıyoruz.
Son olarak da durum geçişlerinde sipariş objemizin durumunu güncelleyerek veri tabanı üzerinde kayıt işlemini yürütüyoruz.
Kullandığımız MassTransit kütüphanesinin de durum makinası kurup kullanmanıza olanak sağlayan özellikleri mevcuttur. Durum geçişleri ve ayarlamarın yapılması noktasında Stateless kütüphanesinin daha sade olması sebebiyle ben bu şekilde bir örnek kurguladım. Ayrıca mesaj kuyruğu olarak MassTransit kütüphanesinin desteklemediği bir platform da kullanılabilir. Bu sebeple daha az bağımlılığa sahip olma adına Stateless kütüphanesinin daha iyi bir seçim olacağını düşünüyorum.
Bu örnek üzerinde Stateless kütüphanesini kullanarak durum makinası kurgusu oluşturduk. Kütüphanenin bu yazıda kullanmadığımız birçok özelliği mevcuttur. Kullanmadan önce bir göz atmanızı tavsiye ederim.