15 Ocak 2022 Cumartesi

Mikroservis Maceram

     S.a. Arkadaşlar,

     Bugünkü yazımda mikroservis projesine nasıl başladığımı paylaşmak istiyorum. Dürüst olmak gerekirse bu yazıyı öncelikle kendime yazıyorum. Çünkü zaman zaman yoğunluklar sebebiyle uzaklaşmak durumunda kalıyorum ve bu yeni edindiğim bilgilerin bir zaman sonra uçtuğunu fark ediyorum. Bu durumdan kurtulmak adına böyle bir yol izliyorum. İkincisi bu işi öğrenmek isteyen arkadaşlara kendi izlediğim yolu, edindiğim bilgileri, okuduğum dokümanları, izlediğim videoları paylaşmak isterim. Umarım hem bana hem okuyan herkese faydası dokunur.

      Öncelikle 2020'nin sonlarına doğru başladığım bir ödev projesinde mikroservis mimarisini basit bir düzeyde de olsa uygulamamı istiyorlardı. Daha öncesinde okuduğum onlarca belki de yüzlerce makale olduğunu söyleyebilirim, ama hiç pratik olarak elimi kirletmemiştim. Yaklaşık 3-4 günlük bir süre sonucunda ortaya bir şeyler çıkarmam bekleniyordu. Basit manada 2 servis ve 1 adet gateway bekliyorlardı. Ayrıca bir servisten diğerine event fırlatarak haber vermemi istiyorlardı. Neyse hızlıca bir araştırmaya başladım tekrardan. .net dünyasında çok popüler olduğu üzere gateway olarak ocelot, message broker olarak da rabbitmq kullanmaya karar verdim. Nispeten alışık olduğum yapılardı. Hızlı yol almak açısından birkaç yazı ve github projesi taradım. İyisiyle kötüsüyle veri tabanı postgresql olan 2 service ve onlara yönlendirme işi yapan 1 gateway, bir servisten diğerine event fırlatan bir ortam kurabildim. Projem kabul edildi :) Oldukça yorucu bir süreç geride kalmıştı, ama asıl süreç bundan sonra başlayacaktı, onu tabii ki şuan söyleyebiliyorum.

    Belirli bir süre geçti, oradaki proje öylece duruyordu, fakat aklımın bir köşesinde o projeyi geliştirmek vardı. Hazır başlamışken bu projeyi devam ettirmem gerektiğine karar verdim ve okuduklarımı uygulamaya başladım. Bu süreç tabii çok kolay geçmiyordu. Bu konu monolit yapı kadar olgunlaşmadığı için bir sürü farklı düşünce, yapılar ve söylemler bulunmakta, hala hızlı bir şekilde gelişmeye ve değişmeye devam ediyor. Hangisinin iyi, hangisinin daha iyi olduğu durumlar değişkenlik gösterebilir. Sonuç olarak herkes kendi tecrübesine göre bazı bilgileri aktarıyor haklı olarak. Bu da benim tecrübem. Eksiklerim varsa beni düzeltmenizden mutluluk duyarım.

    Yazıyı yaptığım commit'lere göz atarak ilerletmenin daha doğru olacağı kanısındayım. Takdir ederseniz ki uzun bir vakit oldu, hem süreci doğru hatırlamak açısından hem takip edebilirlik açısından daha iyi olabilir. Bu şekilde ilerlemem belki konu açısından doğru olmayabilir, ama kendi ilerlemem bu şekilde oldu. Çünkü bazı şeyler öğreniyor ve bu öğrendiklerimi uygulamaya çalışıyordum. Ben de yolun başında sayılan hevesli biriyim. O yüzden kimseyi yanlış yönlendirmek istemem. 

    Ocelot ile başlamak galiba en doğrusu. Dışarıdan gelen istekleri doğru servislere yönlendiren ilk yapımız diyebiliriz. Ocelot'un bir çok desteklediği özellik bulunmakla birlikte ben sadece routing ve dokümantasyon için kullandım. Bu merkezi yapı sayesinden gelen tüm istekleri yönlendirilmesi gereken servislere göndermeyi başardım.

    Rabbitmq ile devam edebiliriz. Rabbitmq hakkında çok yazı, çok söylenecek söz var, ben tüm yapıları projede kullandığım haliyle anlatmayı düşünüyorum. Aksi halde bu yapıların her biri, bir yazı serisi olabilecek konulardır. Bilindiği üzere her servis tek başına bağımsız bir projedir. (Hatta bu yüzden bazı konularda kod tekrarları dahi kabul edilebilir olarak görülmektedir.) fakat bazen bir projemizin başka bir projeyi tetiklemesi gerekebilir. Çünkü a servisinde yaptığımız işin sonucuna göre b servisini de tetiklememiz gerekebilir. Tam da bu nokta rabbitmq kullandık. Publisher-Consumer design pattern'ini kullandım. Yapılan a servisindeki işlemin sonucu başarılı olduğunda b servisi bunun haberini alıyor ve orada ne yapması gerekiyorsa onla ilgili işlemi yapıyor. Bir servis bunu yayımlarken, diğer servis bunu kullanıyor (tüketiyor). 

    Masstransit bu projedeki belki de en kritik nokta olabilir. Masstransit kullanımı sonrası artık hiçbir şey eskisi olmadı :) Masstransit kendini "light service bus" olarak tanımlasa da gayet yetenekli açık kaynaklı bir bus servistir. Açık kaynaklı olmasına rağmen doküman konusunda gayet iyi ve oldukça da yaygın olarak kullanılmaktadır. Saga pattern, log, exception, mediator, test, message retry, health check, rate limit, circuit breaker gibi birçok implementasyonu var. Bu belirttiğim tüm implemantasyonları bu sayede kolaylıkla yapabildim. Burada da Chris'in kendi youtube kanalında topladığı güzel bir playlist mevcut. Resmi sitede implementasyonundan bahsettiği tüm konular için videolar hazırlanmış. Bu konuyla ilgili söylenecek çok şey var. Şimdilik biz burada bırakalım.

    Jwt implemente eden yeni bir Auth servis yazdım. Bahsettiğim 2 servise ek olarak login işlemleri için yeni br servis oluşturdum. Buradaki veritabanı diğerlerinin aksine postgresql değil, mssql oldu. Bunun açıkçası özel bir sebebi yoktu. Farklı servislerde farklı veritabanları da kullanmak için yaptım. Burada alınan token sonrası diğer  servislerimize başarılı bir şekilde gidebiliyoruz.

    Swagger ve SwaggerForOcelot api tasarım, dokümantasyon ve test için kullanılabilen bir araçtır. Her servis için swagger implementasyonu yapmıştım, fakat okumalar ve araştırmalarım devam ederken Feyyaz Acet'in güzel bir yazısına denk geldim. Her servis için ayrı ayrı swagger implementasyonu yapmakla birlikte (çünkü her biri kendi başına bağımsız bir projedir) ocelot ile merkezi bir yapı da kurdum. Bu sayede her bir servisin adresi ile ayrı ayrı uçları kontrol etmeme gerek kalmıyordu. Her birine bir yerden erişebiliyordum (genel ms mantığına ters düşebilir, ama gerçek hayat böyle ...)

    Serilog ve SeqServer ikilisini log mekanizması için kullandım. Mikroservis çerçevesinde log işi bile takip edilmesi zor hale gelebilmektedir. Çünkü servisler arası geçişte bunu iyi takip etmeniz gerekecektir. O yüzden merkezi bir yapı kurmak gerekmektedir. İsteğin atıldığı ilk noktadan sonuçlamasına kadar benzersiz bir id (Correlation Id) ile takip edilmesi de gerekmektedir. Daha sonra bu atılan logları merkezi olarak görüntülemesi de gerekmektedir. Bu amaçla da seq server kurup onu kullandım.

    ELK yine log nedeniyle kullandım. Elastich search, log stash, kibana 3lüsünü kullanmaya karar verdim. Elk'yı kullanmamın sebebi seq'in ücretli olmasının yanı sıra, elk'nın daha geniş kitleler tarafından kullanılması ve açık kaynaklı oluşudur. Öğrenmek amacıyla kullandım, ayrıca elastic search'in sağladığı esneklik ve arka tarafta çalışan yapıyı anlatmaya da sanırım gerek yok. Bu konuyla ilgili de Orhun Beğendi'nin bu ve Burak Selim Şenyurt'un şu yazılarını da görmezden geçmeyeyim.

    HealthCheck mikroservis için olmazsa olmaz yapılardan biridir. Servislerin çalışırlık durumu, üzerindeki yük ve bir çok metrik takip edebileceğimiz bir yapı. Bununla birlikte servislerin çökme anında slack üzerinden bildirim atıyorum. Mass transit'in health check implementasyonu zaten mevcuttu, onunla birlikte slack ile entegre çalışır hale getirdim. Ayaktaki servislerden herhangi biri düştüğünde bildirim gelmektedir. Burada Semih Şenvardar'ın bu yazısını es geçmeyelim. Ayrıca bu konuyu daha ayrıntılı ele aldığım şu yazıma da göz atmakta fayda var.

    Transaction yönetimi belki de mikroservis tarafındaki en karmaşık işlerden biridir. Bunu monolit yapıda yönetmek dahi zor olabiliyorken onlarca servisin olduğu bir yapıda yönetmek çok daha zor olsa gerek. Burada birden farklı pattern, yöntem mevcuttur. Bu konuda Suat Köse'nin çok güzel bir yazısı var. Herkesin ihtiyacına göre gereksinimi değişse de bu konudaki en iyi yöntem SAGA pattern gibi gözükmektedir, fakat yönetimi maalesef o kadar da kolay değil. Projede beni en çok zorlayan kısım burası oldu diyebilirim. Konuyla ilgili sayısız yazı ve proje inceledim, ama bu konuda incelediklerim arasında Selçuk Usta'nın şu projesi en iyisi diye düşünüyorum. Bununla birlikte Gökhan Gökalp'in bu yazısına da göz atmakta fayda var. Ben de bu vesileyle global klasörü altına saga'yı implemente ettim. 

    Jenkins yazılan kodları, yapılan işlemleri otomatize etmeye yarayan CI aracıdır. Github projem ile entegre ettikten (sanırım public olduğu için buna izin veriliyor, aksi durum ücretli olabilir. Gitlab buna ihtiyaç duymadan yapıyor olabilir, araştırmanızı öneririm.) sonra yapılan her commit sonrası (farklı şekillerde tetiklenebilir) projeyi otomatik olarak ayağa kaldırmaya çalıştım. Docker ve windows konusunda en çok işkence çektiğim konulardan biri oldu. En sonunda manuel olarak windows üzerinde ayağa kaldırdım (maalesef). Salih Cantekin'in şu videosuna göz atmakta fayda var.

      Docker mikroservis yapısı kullanıyorsanız olmazsa olmaz yapılardan biridir (ne çok şeye olmazsa olmaz dedim, ama maalesef öyle.) Her proje için bir dockerfile ve tüm bunları çağıran bir compose dosyası oluşturdum. Bir özet niteliği taşıyan şu yazıma bakmakta fayda olabilir. Docker ayağa kaldırırken en çok zorlandığım 2 konu: https ve jenkins oldu. Https sertifika konusunu sadece http desteği sağlayarak, jenkins için ise manuel kurulum yaparak çözdüm.

      Uygulanan Pattern ve Yaklaşımlar

 - DDD (Domain Driven Design): Karmaşık sistemlerde yazılımın yaşam süresini artırmaya hedefleyen bir yaklaşımdır. Bununla birlikte herkesin anlayacağı ortak bir dil hedefler, böylece sadece yazılımcıların değil tüm teknik ekibin bir dili olur.

- Saga Pattern: Yukarıda da belirttiğim gibi transaction yönetimi sağlayan yapılardan biridir. Veri tabanı işlemlerinin herhangi bir aşamasında meydana bir hata gelirse tüm süreç geri alınır. Tüm bu işlemler bir bütün olarak ele alınmış olur (atomicty)

Mediator: Nesnelerin birbirine olan bağımlığı azaltan/yöneten bir design pattern'dir. Nesnelerin birbiriyle olan yönetimi karmaşık hale geldiğinde bunları kendi arasında yapmaları oldukça zorlaşır. O yüzden merkezi bir yapı üzerinden haberleşmeleri sağlanır. Bu şekilde yönetim kolaylaşır.

CQRS: Bir metod nesnenin ya değerini değiştirmeli veya bir değer döndürmeli prensibine dayanan bir pattern'dir. Commands veri değiştiren, queries ise geriye değer döner. Karmaşık sistemlerde kullanılması tavsiye edilir.

Circuit Breaker: Sisteme aşırı derece yüklenildiğinde bunu yönetmeye çalışan bir devre elemanı olarak ele alabiliriz. Yoğun istek sonrası belirli bir süre askıya alır daha sonra kontrollü geçişi benimser, eğer sistem düzgün olursa tekrardan çalışır hale getirilir aksi halde kesintiye devam edilir.

Event Sourcing: Nesnenin son durumunu saklamak yerine, nesnenin tüm durumlarını ele almayı hedefler. Gelen istek üzerine sadece son durum değil, tüm event'leri birleştirerek sonucu döner.

Publish-Subscribe: Publisher'in alıcıları belirtmeden ya da bu alıcıları bilmeden haber göndermesidir. Buna üye olanların ise sadece ilgilendikleri bilgileri almasını sağlar.

       Kısa kısa

- Mehmet Özkaya'nın bu ve Fatih Çakıroğlu'nun ondan esinlendiğini düşündüğüm fakat daha da geliştirdiği şu projelere göz atmakta fayda var. İkisinin de Udemy'de eğitimleri var. 1.si İngilizce, 2.si ise Türkçe'dir. 

- Suat Köse'nin yazılarından derlediği kitabına göz atmakta fayda var.

- Elif Akgün'ün çalıştığı notları derlediği şu bloga göz atabilirsiniz.

- İbrahim Kürce'nin derlediği mikroservis dünyasında kullanılan araçlar yazısına göz atabilirsiniz.

- Süleyman Fazıl Yeşil'in okurken hiç bitmese dediğim işlem bütünlüğü yazısına da göz atabilirsiniz.

    Teşekkür

- Yazı ve projelerinden yararlandığım yazıda referans verebildiğim ve veremediğim tüm herkese

- Projeyi geliştirirken zaman zaman konuştuğum, kendileriyle bazı konuları tartıştığım arkadaşlarımdan Fatih Güner ve Akif Feyzioğlu'na 

- Projeyi geliştirirken kendisinin zamanından aldığım eşim Menal ve biricik oğlum Ömer Nevzat'a 

teşekkürlerimi iletirim.

Hiç yorum yok:

Yorum Gönder