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

Microservice – Saga – Commands/Orchestration Implemantasyonu İle Transaction Yönetimi

Merhaba,

Önceki içeriklerimizden Microservice – Saga – Events/Choreography Implemantasyonu İle Transaction Yönetimi başlıklı yazımızda Saga pattern’ının iki implemantasyonundan biri olan choreography yöntemini tüm detaylarıyla ve pratik olarak incelemiştik. Bu içeriğimizde ise yine Saga pattern’ının ikinci implemantasyon yöntemi olan Orchestration’ı pratik olarak tam teferruatlı inceliyor olacağız. Yazımız uzun ve meşakkatli olacağından dolayı vakit kaybetmeksizin buyrun hemen başlayalım…

Events/Choreography yöntemi varken Commands/Orchestration implemantasyonunun amacı nedir?
Herşeyden önce bu soruyu sorarak başlamakta fayda görmekteyim. Choreography implementasyonu neyimize yetmedi de Orchestration yönteminden medet umar hale geldik konuşmakta fayda var 🙂

Yandaki şemaya göz atarsak eğer Choreography implementasyonunun stratejik yapılanmasını görmekteyiz. Dikkat ederseniz Choreography’de tüm servisler Event Broker ile iletişim kurmak ve hangi servisten hangi event’in geleceğiyle ilgilenmek mecburiyetindedir. Haliyle Choreography adı üzerinde bütünsel bir koreografiye ayak uydurulabilir bir inşa gerektirmekte ve bazen aynı işi birden fazla farklı servisten gelebilecek şekilde koordine ederek farklı consumer’lar da yapılmasını gerektirebilmektedir.

Bunu daha da somutlaştırabilmek için basit bir e-ticaret senaryosundan örneklendirme yapabiliriz. Misal; bir e-ticaret sisteminde müşteriler tarafından yapılan siparişler sırasıyla Order.API, Stock.API ve Payment.API servisleri üzerinden yürütüldüğünü varsayalım. Bu sistemde Stock.API ya da Payment.API servislerinde oluşabilecek herhangi bir aksi duruma istinaden Order.API gerekli müdahalede bulunabilmek ve siparişi iptal edebilmek için her iki servisten gelecek başarısızlık durumunu ifade eden event’leri consume etmek mecburiyetindedir. Haliyle hangi servisten gelirse gelsin bir siparişin iptal edilmesi aynı işi gerektireceğinden dolayı Choreography implementasyonu burada bu operasyonun çoklatılmasını mecburi kılmakta ve aynı işi hem Stock.API için hem de Payment.API için consume edecek şekilde kodlanılabilir olmayı gerektirmektedir.

Bu yüzden Choreography implementasyonu hem lüzumsuz kod israfı, hem de yönetilebilirlik açısından 2 ile 4 servisin söz konusu olduğu çalışmalarda ideal olarak sınırlandırılmaktadır. Daha fazla servisin söz konusu olduğu durumlarda ise artık servisler arası yönetilebilirlik maliyetini düşürmek için merkezi bir yönetime olan ihtiyaç oldukça artmaktadır. İşte artık bu sınırda yeni implementasyon kabulü olarak Orchestration devreye girmektedir.

Orchestration implementasyonu, servisler arasındaki distributed transaction’ı sağlayabilmek için merkezi bir orchestrator üzerinden tüm trafiği yönetmekte ve böylece servisler açısından eylemlerin kaynağıyla ilgilenmeksizin hangi event’ler de hangi aksiyonların alınması gerektiğinden başka ekstra bir bilginin bilinmesine gerek duyulmaksızın çalışılmasını sağlamaktadır.

Yukarıdaki örnek e-ticaret senaryosundan devam edersek eğer bu sefer Order.API, Stock.API ve Payment.API servislerinin yanında bu servisler arasında iletişimi yani gerekli organizasyonu sağlayacak olan bir Orchestrator servisi olacaktır. Artık Stock.API veya Payment.API servislerinden herhangi birinde olabilecek olası bir aksilik durumunda gerekli event’lerle bu Orchestrator servisi tetiklenecek ve yine bu servis üzerinden gerekli sipariş iptal event’i Order.API‘ya iletilecektir. Haliyle burada Order.API artık Orchesrator kanadını dinleyeceğinden dolayı iptal durumunun hangi servisten geldiğiyle ilgilenmeksizin sadece ilgili event’in consume edilmesine odaklanacak ve böylece servisler arası iletişimde yönetilebilirlik hat safhaya çıkacaktır.

Orchestration implementasyonu nasıl gerçekleştirilmektedir?

Orchestration, yukarıdaki paragrafta da değinildiği gibi merkezi bir orchestrator aracılığıyla distributed transaction’ın yönetilmesini sağlayan bir stratejiye dayanmaktadır. Haliyle bu strateji kabul edildikten sonra geriye yapılması gereken bu orchestrator sorumluluğunu üstlenecek olan servisin bir şekilde geliştirilmesi kalmaktadır. Misal olarak bu servis yukarıdaki şemada olduğu gibi salt bir şekilde message broker üzerinden merkezi haberleşme birimi olabilecekken, bir yandan da State Machine olarak tasarlanmış ayrı bir worker servis olabilir. Ki bizler Saga pattern operasyonlarında orchestrator olarak state machine oluşturarak operasyon sürecindeki her bir adımı ayrı ayrı kontrol edecek ve o anki duruma göre event yönetimini sağlayarak distributed transaction yönetimini state yapılanması üzerinden yönetmeyi tercih edeceğiz. Neden mi? Çünkü bu yöntem best practices olarak kabul görmektedir. Ve bu makalede de bu yöntem üzerinden pratik bir senaryo icra edilecektir.

Peki neden ve nasıl state machine kullanacağız?
Yine bu soruya yukarıdaki örnek e-ticaret senaryosu üzerinden cevap vermeye çalışırsak eğer kullanıcı tarafından yapılan sipariş isteği Order.API tarafından karşılandıktan sonra state machine OrderCreated durumunu tutacaktır. Ardından siparişteki ürünlerin stok bilgileri Stock.API tarafından güncellendikten sonra state machine’de ilgili siparişe dair durum değeri StockReserved olarak güncellenecektir. Benzer mantıkla stok bilgilerinde herhangi bir tutarsızlık söz konusu olursa eğer StockNotReserved durumu tutulacak yok eğer ödeme sürecinde bir hata söz konusuysa PaymentFailed durumu tutulacaktır. Ödeme başarılıysa eğer ilgili siparişe dair state Finalize yapılarak nihayetinde silinecektir. Haliyle bir sipariş sürecindeki tüm adımlar state machine tarafından tutulacağı için ilgili siparişin hangi durumlarda aksadığını(ya da aksi durum yaşadığını) veya tamamlanıp tamamlanmadığını rahatlıkla görebilecek ve yönetebileceğiz.

State machine’i nasıl kullanacağız sorusuna gelirsek eğer, burada State Design Pattern ile kendi state’lerimizi yönetebilecek bir tasarım ortaya koyabileceğimiz gibi daha efektif ve hızlı çözüm getirebilmek için bizlere hazır bir state machine yapılanması sunan open source Automatonymous kütüphanesinden de istifade edebiliriz. Bu kütüphaneyi salt bir şekilde belirtilen adresindeki yönergeler doğrultusunda uygulayabilir ve kullanabiliriz. Lakin bizler genellikle message broker çalışmalarında Enterprise Service Bus(ESB) kullanmayı tercih ettiğimiz ve bundan dolayı genellikle MassTransit kütüphanesini kullandığımızdan dolayı MassTransit geliştiricileri state machine yapılanmasını da destekleyebilmek için ayrıca bir geliştirme yapmak yerine direkt olarak Automatonymous kütüphanesini bünyelerine katmış olduklarından bizler bu Automatonymous kütüphanesini MassTransit kütüphanesi üzerinden kullanıyor olacağız.

Haliyle artık çok uzatmadan örnek senaryomuzun sınırlarını çizelim ve gerekli projelerin oluşturulmasını ve kullanılacak kütüphanelerin kurulumunu gerçekleştirelim.

Servislerin Oluşturulması

Bu senaryomuzda esasında bir önceki Microservice – Saga – Events/Choreography Implemantasyonu İle Transaction Yönetimi başlıklı makalemizde ele aldığımız senaryoyla birebir aynı olacaktır. Lakin implementasyon olarak choreography yerine orchestration kullanacağımızdan dolayı ayrıca bir state machine görevi görecek servisimiz daha olacaktır.

Yine de senaryonun genel hattını tekrar çizmemiz icap ederse; bir e-ticaret sistemindeki sipariş sürecini modelliyor olacağız. Bu süreçte Order.API, Stock.API ve Payment.API isimli servisler sorumlu olacak ve sırasıyla işlevsellik göstereceklerdir. Ayrıca bu servisler arasında Saga orchestration implementasyonuna uygun bir şekilde merkezi distributed transaction yönetimini sağlayacak olan SagaStateMachine.Service adında worker service oluşturulacaktır. Tüm bu servisleri oluşturabilmek için aşağıdaki dotnet cli komutlarından istifade edebilirsiniz;

(Service) dotnet new webapi --name Order.API
(Service) dotnet new webapi --name Stock.API
(Service) dotnet new webapi --name Payment.API
(Worker Service) dotnet new worker --name SagaStateMachine.Service
(Class Library) dotnet new classlib --name Shared : Servisler arası mesajlaşma için event’leri vs. bu class library’de oluşturacağız.

Servislere MassTransit Kurulumu ve Temel Konfigürasyonlar

Saga pattern kullanıyorsak eğer asenkron bir iletişim modeli olmazsa olmaz bir yaklaşımdır. (bknz: önceki makale/choreography) Dolayısıyla bizler bu asenkron iletişim sürecinde message broker olarak RabbitMQ‘yu tercih edeceğiz ve yukarıdaki paragraflarda bahsedildiği üzere bu message broker’ı daha efektif hale getirebilmek için MassTransit Enterprise Service Bus kütüphanesinden istifade edeceğiz. Dolayısıyla yukarıda oluşturulan tüm servislere bahsi geçen ilgili kütüphaneleri aşağıdaki dotnet cli komutları eşliğinde yükleyelim.

Akabinde SagaStateMachine.Service haricindeki tüm servislerin ‘Startup.cs’ dosyasında aşağıdaki konfigürasyonları gerçekleştirerek, RabbitMQ entegrasyonunu sağlayalım.

    public class Startup
    {
        .
        .
        .
        public void ConfigureServices(IServiceCollection services)
        {
            .
            .
            .
            services.AddMassTransit(configure =>
            {
                configure.UsingRabbitMq((context, configurator) =>
                {
                    configurator.Host(Configuration.GetConnectionString("RabbitMQ"));
                });
            });

            services.AddMassTransitHostedService();
            .
            .
            .
        }
        .
        .
        .
    }

İdeal olarak her zaman yaptığımız gibi gerekli connection bilgilerini yapılandırma dosyasından yahut environment’tan talep etmekteyiz. Misal ilgili bilgilerin ‘appsettings.json’ dosyasından geldiğini varsayarsak eğer ilgili dosyanın içeriği aşağıdaki gibi olacaktır.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "RabbitMQ": "amqps://l***kqjU9x1bR@cattle.rmq**om/ll***"
  }
}

Burada RabbitMQ için cloudamqp.com adresindeki cloud ortamda yaratılan instance’a uygun bir bağlantı verilmiştir. Önceki makalede de RabbitMQ’nun cloud ortamına dair herhangi bir fikriniz olmadığı taktirde RabbitMQ – Cloud Ortamda(CloudAMQP) Kurulumu başlıklı makaleye göz atmanızı tavsiye etmiştim. Ve hala ediyorum 🙂

SagaStateMachine.Service‘in Yapılandırılması

Şimdi Saga orchestration implementasyonunun merkezi olan ve state machine’e karşılık gelen SagaStateMachine.Service‘in konfigürasyonlarını gerçekleştirelim.

SagaStateMachine.Service, özünde bir worker service’dir. Yani arkaplanda çalışan bir servis. Haliyle içerisinde ‘Startup.cs’ olmayan bu worker servis uygulamasının konfigürasyonlarını yapabilmek için ‘Program.cs’ dosyasındaki ‘CreateHostBuilder’ isimli metot içerisinde çağrılan ‘ConfigureService’ metoduna aşağıdaki müdahalelerde bulunmamız gerekmektedir.

    public class Program
    {
        .
        .
        .
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    .
                    .
                    .
                    services.AddMassTransit(configure =>
                    {
                        configure.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
                        {
                            cfg.Host(hostContext.Configuration.GetConnectionString("RabbitMQ"));
                        }));
                    });

                    services.AddMassTransitHostedService();
                    .
                    .
                    .
                });
    }

Evet… Aslında yukarıdaki verilen tüm kodlar ilerideki adımlarda mevcut hallerinden daha da gelişerek bizlere eşlik edecektir. Şimdilik en temel konfigürasyonlarla yolculuğumuza başlamaya hazırız.

Servislerin Geliştirilmesi

Şimdi içeriğimizde sınırları çizilen senaryoya uygun bir şekilde Saga orchestration implementasyonu eşliğinde servislerimizi adım adım geliştirebiliriz. Tabi ki de burada pratiksel olarak ele alınan uygulamanın herhangi bir adımının makalenin seyrine göre farklı ihtiyaçlara istinaden değişikliğe uğrayabilme ihtimali vardır. Dolayısıyla iyi takip etmenizi ve hatta gerekirse sürece sizlerin de pratiksel olarak eşlik etmenizi tavsiye ederim… Buyrun başlayalım…

Vee tüm işlemlerimiz bitti. Evet, biliyorum… İnşa süreci biraz zahmetli ve meşakkatli geçmiş olabilir. ☺ Ama şimdi sıra, yapılan tüm bu çalışmaların meyvesini toplamaya gelmiş bulunmaktadır. Ha gayret son kez test edelim şu yapılanmayı…

Test Edelim

İlk olarak tüm servisleri ayağa kaldırdığımızda RabbitMQ ile connection sağlayıp sağlamadıklarını gözlemleyelim.
Görüldüğü üzere tüm servislerimiz başarıyla bağlantı sağlamış bulunmaktadır. Şimdi aşağıdaki gibi tutarlı verilerle bir sipariş isteğinde bulunalım ve süreci adım adım takip edelim.
Bu verilerle istek yapıldıktan sonra sipariş oluşturuluyor ve State Machine veritabanında ilgili siparişin durumu ‘OrderCreated’ olarak ayarlanıyor.
Ardından stok bilgileri MongoDB’de aşağıdaki gibi güncelleniyor.
Tabi bu durumda State Machine veritabanında ilgili siparişe dair durumumuz ‘StockReserved’ olarak güncelleniyor.
Ve ardından ilgili sipariş durumu ‘Orders’ tablosunda Completed olarak güncelleniyor ve ardından State Machine’den siparişe dair veri kaldırılıyor!
Sipariş isteğini tutarsız verilerle yaparsak eğer burada hem sipariş hatalı bir şekilde sonlandırılmış olacak hemde State Machine veritabanında bu siparişe dair sonlanışın neden olduğu state bilgisini rahatlıkla görebiliyor olacağız.

Misal; tutarsız stok bilgileriyle bir istek yaparsak eğer son görüntü aşağıdaki gibi olacaktır;
Yahut ödeme durumunda bir tutarsızlık oluşursa sistemin son hali aşağıdaki gibi olacaktır;

Nihai olarak;
Oldukça uzun ve zahmetli bir içerik ile Saga pattern’ı Orchestration implementasyonu eşliğinde ele almış bulunmaktayız. İçeriğimiz okunurken elbette yer yer zor yahut karışık geliyor olabilir. Lakin bu kadar kompleks ve bol kritik gerektiren bir konuyu yazıya aktarabilmek sizlerin okurken sarfettiğiniz enerjiye eşdeğer bir zahmet gerektirdiğine emin olabilirsiniz. Elbet bir niyet ile başlayan bu uğraşın neticesini iyi kötü getirebilmenin sevinciyle hepinize iyi okumalar diliyor ve bu noktaya kadar sabredip eşlik ettiyseniz tüm samimiyetimle teşekkür ediyorum…

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

Not : Örnek projeyi indirebilmek için buraya tıklayınız.

Mobil sürümden çık