SAGA mikroservis uygulaması - 7

Eğer internette gezinirken bir şekilde bu yazıya denk geldiyseniz bilgi seviyenize göre 2 seçeneğiniz var. Eğer tecrübeli bir developer iseniz ve spring cloud ile mikroservisler konusunda bilginiz varsa github 'dan bu projeyi fork ile alabilir ve config repository 'yi indirerek projeyi bu yazıya göre ayağa kaldırmaya çalışabilirsiniz. Eğer tecrübeli bir developer değilseniz yazı dizisinin ilk yazısından başlamanız gerekiyor. Bu yazıda hazırladığım mikroservis projesini analiz edecek ve bazı farklı araçlardan ve alternatif yaklaşımlardan bahsedeceğim. Bu araçları bu yazıda tanıtacaktım fakat yazı çok uzayacaktı. Bu yüzden o konu başka yazıda olacak. Burada "ne yaptık", "neden yaptık", "doğru yaptık mı" ve "ne elde ettik veya kaybettik" gibi sorular soracağım. Bahsi geçen mikroservis mimarisi uygulamasının mimarisi aşağıdaki resimdeki gibi.

Ne yaptık

Bu uygulama ile mikroservis ile çalışan bir biletleme uygulaması oluşturmuş olduk. Omurga sistem olarak Gateway, Discovery Server ve Config Server kullandık. Küçük servislerimizi Eureka 'ya bağladık ve load balance yaptırdık. Gateway ile gelen istekleri Eureka 'ya path 'lere göre yönlendirdik ve erişilememe durumundaki routing 'ler ile resilience sağladık. Sonrasında küçük servisleri ve failover 'larını REST endpointleri ile tasarladık ve buralarda iş akışlarına dair kodlar yazmadık. Kullanıcılar, etkinlikler ve ödeme sistemi fonksiyonları için metodlar yazdık ve bunları küçük servislerde gruplandırdık.

Bu projeler kodlandıktan sonra bir tüketici MVC web uygulaması yazdık ve burada SAGA 'lar şeklinde iş akışlarını kodladık. Bu MVC uygulaması JWT header bilgileri ile request 'leri gateway 'e ileterek küçük servislere erişti. Ayrıca koordinasyon görevini üstlenerek servis çağrılarını ve response 'lara göre hataları da yönetti. Config Server ise spring boot konfigürasyonlarını aynı noktada (github) toplayabilmemize ve buradan dağıtabilmemize olanak sağladı. Kullandığım veritabanları MySql veritabanları idi fakat mikroservis açısından hayati bir önem arz etmiyordu. Bütün bu altyapının sponsorluğunu tabi ki spring cloud üstlendi :)

Bu uygulamaya domain driven design ile başlayıp bounded context 'ler tanımlayabilirdik. Ben kendi kararlarımı dikte ederek bu kısmı bir bakıma atlamış oldum. Bana sorarsanız bu sistemde kullanıcı, etkinlik ve ödeme işlemi bir bounded context olabilirdi çünkü bu kavramlar herkes tarafından ortak bir anlama sahip olabilir. Tabi bu düşüncem hatalı da olabilir. Yorumlarda bu sistem için domain driven design 'ı nasıl uygulayacağım konusunda sizin fikrinizi de duymayı isterim.

Burada gateway pattern kullanmış olduk ama konu mikroservisler olunca bu sadece bir isimden ibaret bence. Çünkü aynı zamanda SAGA ve circuit breaker desenlerini de kullandık. Eğer bir yazılımcıya bu web servisleri design pattern 'ler ile ifade ederseniz eminim herkes kafasında farklı bir şema çizecektir. Bu yüzden yazı dizisi boyunca tasarım desenlerinden bahsetmedim. Bu desenler konusunda bilgi sahibi olun tabi ki ama doğru deseni kullanmanız mikroservislerinizin başarılı bir şekilde yaşayıp yaşayamayacağını belirleyecektir. Yani NEDEN sorusu önemli aslında. Sebeplerimize (benim sebeplerime aslında) bir bakalım.

Sebepler

Aldığınız her karar bir sebebe dayandırılmış olmalı. Bu sebep de sadece "ben böyle biliyordum" veya "bana böyle dediler" gibi olmayan mantıksal veya pragmatic bir sebep olsa iyi olur tabi. Ben bütün sistemde teknolojiler ve araçlarla ilgili birkaç karar aldım tabi.

  • Spring cloud teknolojilerini kullandım çünkü ben böyle biliyordum :) Evet şu anda gülebilirsiniz. Bu araçlar için tabi ki farklı alternatifler var ama gateway sayesinde java kodu ile routing yapabildim ve Eureka service discovery ve registery için en kolay çözümdü benim için.
  • İş akışlarını SAGA 'lar olarak değerlendirdim çünkü her birini başı ve sonu olan bir hikaye olarak gördüm. Evet sagaların genelde veri tutarlılığı için kullanıldığını ve SAGA yönetimi için araçlar olduğunu biliyorum fakat ben bu konuyu sadece tutarlı veritabanı transaction 'ları olarak değerlendirmiyorum.
  • Bu sistemde işlem tutarlılığının resilience 'dan yani servislere erişimin stabil olmasından daha önemli olduğunu düşündüm. Bu sistemde bir bilet almak için birkaç saat beklemeniz sorun olmayacaktır. Ayrıca web sitesinde saniyede 10 bin işlem gibi bir yük beklemiyorum. Burada netflix yapmıyoruz hani.
  • Mesaj tabanlı yaklaşım yerine koordinatör olarak MVC uygulamasını tepeye koydum. Çünkü bu biletleme uygulaması için gateway ve load balance özelliklerinin yeterli olacağını düşündüm. Ağır yük altında çalışmak gibi bir korkumuz olmadığı için.
  • İş akışlarını koordinasyonla düzenledim bu sayede kolayca kodlayabilmiş oldum. Aksi takdirde mesaj tabanlı bir sistem kurgulamam gerekecekti ve bu da öğrenme aşamasında çok zor olacaktı. (Koordinatör ve mesaj kuyruğu ile ilgili daha fazlası sonda)
  • Güvenlik için JWT kullandım çünkü servislerdeki bazı işlemler kullanıcı girişi gerektirirken bazıları gerektirmiyordu. Pek fazla güvenlik kaygım olmadı ve Restful servisler için token tabanlı güvenliğin best practice olacağını düşündüm.
  • Sistemde 3 fault tolerance yapısı tasarlamış oldum. İş kuralları hataları, exception 'lar ve network hataları. Network hatalarını resilience4J ile halletmiş oldum. Diğer ikisi ise MVC uygulaması içerisinde hikaye gibi yani bir SAGA gibi basit seviyede yönetildi.
  • En önemlisi ise sistemi mikroservis olarak gerçekleştirmeye karar verdim çünkü bir mikroservis uygulamam olsun istiyordum :) Bu uygulamayı monolitik uygulama yerine mikroservisler ile gerçekleştirmek zorunlu muydu? Belki evet belki hayır.

Bunlar hatırladığım kadarıyla yolculuk boyunca aldığım en önemli kararların sebepleri idi. Bu uygulamaya mikroservis kodlama deneyimim olmadan başladım ve bu yüzden süreçte bazı parçalar değişti. Ama sonuçta yukarıdaki resmin oluşmasını bu sebepler sağladı. Benim nedenlerim ve çıkarımlarım yanlış olabilir veya tartışılabilir. Bu sorunlara veya isterlere daha iyi çözümler de üretilebilir. Ama her ne olursa olsun neden sorusuna cevap verebilmeniz gerekiyor. Sonuçta bu sistemi kurgulayarak birşeyler elde etmeyi planlıyorsunuz. Bu da sıradaki başlığı getiriyor.

Kazanımlar

Aldığım kararlarla bazı kazanımlar elde ettiğimi düşünüyorum.

  • Bu sisteme istediğiniz kadar yeni servisi kolayca ekleyebilirsiniz. Bu aslında mikroservis mimarisinin de bir getirisi. Tecrübesiz developer 'lar için benim yazdığım gibi bir rehber yazarsanız geliştirme işlemi de kolay olabilecektir.
  • Küçük servisler ise istediğinz bir veritabanı teknolojisine bağlanabilir ve istediğiniz dilde yazılabilir. Bu da aslında mikroservis mimarisinin bir getirisi.
  • İş kurallarına dair kodlar MVC uygulaması içerisinde olduğu için bir hatayı incelemek için kodu debug etmek kolay olacaktır. Bu yüzden dağıtık takip (distributed tracing) araçlarına da ihtiyaç duymadım. Ayrıca sisteme yeni bir iş gereksinimi eklemek de bir monolitik uygulamaya göre daha kolay olacaktır.
  • Servisler arasında makarna ilişki bulunmuyor. (Bazıları makarna koddan kaçınayım derken makarna servislere dönüştürüyor)
  • İş akışları ile ilgili kodlar MVC uygulamasında olduğu için küçük servislerim mikro seviyede kalabildi. Aksi takdirde makroservis gibi bir yapıya dönüşürdü.
  • Util projesindeki sabitler ve POJO 'lar MVC uygulaması ile servisler arasındaki veri transferini kolaylaştırdı.
  • Config server sayesinde bazı boilerplate ayarları merkeze alabilmiş oldum.
  • Gateway ve Eureka çok temel işlevleri yerine getirdikleri için ağır yük altında ezilmeyeceklerdir diye düşünüyorum.
  • İstediğiniz zaman küçük servisleri de parçalara bölebilirsiniz. Zamanla iş gereksinimleri değiştiğinde servisleri adapte etmek zor olmayacaktır.
  • Gateway ve Resilience4J yardımı ile resilience sağlamış oldum.
  • Sanırım en önemli kazanım ise bütün sistemi durdurup deploy etmek yerine servisleri bağımsız bir şekilde geliştirip güncelleyebilmektir.

Eğer kendimi kandırmıyorsam sistemin avantajları bunlar olsa gerek. Burada ileri görüşlü olamayabilirim veya dar görüşlü olabilirim. Başlangıçta gereksinimlerimiz, kısıtlarımız ve hedeflerimiz vardı hatırlarsanız. Sadece hedefleri tutturmuş olmam olası bütün gereksinimleri düşünmüş olduğumu göstermez. Bu aynı zamanda doğru şeyi yaptığımızı veya sorunları doğru yoldan çözdüğümüzü de göstermez. Yorumlarda beni gömebilirsiniz :) Bu mimari ve uygulama hakkında dürüst olmam gerekirse dezavantajlarından da görebildiğim kadarıyla bahsetmem gerekir. Bu kısım mikroservis uygulamanızı işleme aldıktan sonra ortaya çıkar genelde.

Dezavantajlar

  • Zamanla iş gereksinimleri büyüdükçe monolitik görünen bir MVC uygulaması ile başbaşa kalabilirsiniz.
  • Bu sistem bütün çalışma yükünü servislerden alıp MVC uygulamasına yüklüyor. Bu da yüksek işlem gücü gerektirebilir. Bu durumda birden fazla MVC uygulaması kopyası ve bunlar arasında load balance yapılması gibi bir çözüme mecbur kalabilirsiniz.
  • Gateway ve Eureka yüksek trafiğe dayanamayabilir. Şu aşamada bu ikisinin kapasitesini bilmiyorum. Böyle bir durumda ise gateway 'i çoğaltıp önüne Eureka veya başka bir gateway koyarak load balance yaptırabilirsiniz belki.
  • Gateway, Eureka ve Cloud Config bileşenleri tek instance çalışıyor. Bunlardan birisinin bir sebepten durması halinde sistem nasıl davranacağı bilinmiyor. Bunu deneyip test etmeyecek kadar tembelim.

Görünüşe göre dünyadaki en süper mikroservis mimarisi uygulamasını yazmışım. Tekrar söyleyeyim, burada ileriyi yeterince göremiyor veya geniş açı ile bakamıyor olabilirim. Veya taraflı bir şekilde sistemi değerlendiriyor da olabilirim. Bu yüzden bu durumda dışardan bakan bir göz size yeni bir bakış açısı kazandırabilir. Dahası "bunu doğru yaptım mı" sorusunun cevabını dürüst bir şekilde vermem mümkün değil. Ben kendi gereksinimlerimi çıkardım ve belli kriterlere ve hedeflere göre çözümler ürettim. Bana sorarsanız beklenen hedefleri tutturdum ve doğru yaptım derim. Ama mikroservisler soyut ve değişkenlik gösterebilen kavramlar olduğu için bu konuda genel geçer doğrular yerine ödünleşimler oluyor. Hala benim bir mikroservis uzmanı olduğuma inanmıyorsanız, haklısınız :) Benim de kafamda bazı sorular oluştu ve cevaplarını bulamadım.

Sorular

Yorumlarda bu sorular hakkındaki düşüncelerinizi duymaktan mutlu olurum. Bu sorular biraz da "bu işi doğru yaptım mı" düşüncelerinden kaynaklanıyor.

  • Bu sistemde Eureka önce gelip sonra gateway koyulabilir miydi? Bu şekilde gateway 'ler arasında load balance yapılabilirdi belki. Bu mantıklı olur muydu?
  • Küçük servisler birbirleri arasında bir etkileşime girmiyor. Bu durumda Sleuth ve Zipkin kardeşler gibi distributed tracing araçları kullanmaya gerek kalıyor mu?
  • Bu sistemde Domain Driven Design uyguladım mı? Uygulamadıysam nasıl uygulanabilirdi?
  • MVC ve servisler arasındaki veri şifreli değil sanırım. Bu durumda SSL benzeri bir teknoloji mi gerekli?
  • 3. bir seçenek var mı? Eğer koordinasyon veya koreografi yapmazsam bu ikisinin arasında bir tasarım yapabiliyor muyum?

Bu noktada teknik olarak hazırladığım mikroservis ile işim bitiyor. Uygulamayı bitirmiş oldum ve savunmamı da yaptım. Ama her zaman biraz hayalci veya filozof olma yatkınlığım bulunuyor. Bu yüzden 2 tane "peki ya.." sorusu ve çözümü de var kafamda. Bunlar kendi tecrübemle ürettiğim alternatif çözümler aynı zamanda.

Peki ya SAGA yapıları çok büyürse

İş gereksinimlerinin büyümesi ve karmaşıklaşması ihtimali her zaman bulunuyor tabi ki. Bu durum bence 2 soruna yol açardı. Uzun response süresi ve çok büyük controller veya servis sınıfları. Bu durumda yaptığım sistemi değiştirmezdim. Çünkü aksi takdirde küçük servisleri de iş gereksinimleri ile şişirmem gerekirdi. MVC uygulamasını SAGA 'lara göre molülerleştirmeye çalışabilirdim maven module yapısı ile. Bu en azından kodları birbirinden ayırabilirdi. Ayrıca tasarım desenleri de anlamlı kod yazabilmek adına yardımcı olacaktır. Çünkü aslında MVC uygulamasında bir visitor deseni uyguluyoruz.

Uzun response süresine bir çözüm bulamazdım sanırım. Çünkü ne yaparsam yapayım küçük servisler defalarca çağırılacak. Ama bu çağrımlar MVC uygulamasına fazla yük bindirirse MVC uygulamasını yedekleyip bunlar arasında load balance yapmayı düşünebilirdim. Bütün bunlar mesaj tabanlı yaklaşım ile servisler arası makarna ilişkiler kurmamak için.

Ama ne yaparsam yapayım sanırım koreografi tabanlı mikroservis mimarisi de bazı sorunlar için çözüm olabiliyor. Bir gün bunun için de bir örnek yapabilirim belki. Hali hazırda bir sorum daha var zaten.

Peki ya çok fazla kullanıcı aktivitesi oluşmaya başlarsa (Koreografi)

Bu durumda gateway ve eureka 'ya ihtiyaç duymayabilirdim. Bildiğim kadarıyla sistemin tam ortasına network hub gibi bir message queue (kafka / rabbitmq / activemq) koyabiliyorum. Bütün servisleri de bir dinleyici olarak tasarlayıp mesaj kuyruklarına abone yapabiliyorum. MVC uygulaması bu durumda bir işlem başlatır ve mesaj iletir. Sonraki ilk servis mesajı alır ve kendi işini yapar. Sonrasında gelen payload yani veriye göre veya iş akışına göre veriyi ilgili kuyruğa iletir. Bu bir bakıma Chain of Responsibility ve Mediator tasarım deseninin karışımı.

Ama bütün iş akışlarını düşündüğümde böylesi çok fazla kuyruğa sebep olurdu gibi geliyor ve bu da bence mantıklı değil. Ayrıca çok fena distributed tracing aracı ihtiyacı olurdu, Sleuth + Zipkin veya başka bir tool gerekirdi. MVC uygulaması da bu kuyrukları dinleyen bir yapıya gelmek zorunda kalırdı ve belki de bir reaktif kütüphane kullanması gerekirdi. Koreografi yapısında işleyişi bu şekilde görüyorum.

Özetle..

Bu yolculuğun sonunda emin olduğum tek bir gerçek var. Bu sistemi oluşturan bütün kararlar, araçlar ve bileşen yapıları sorgulanabilir veya eleştirilebilir. Öğrenerek ve kodlayarak bir mikroservis çözümü üretmiş ve araçlarını tanımış oldum. Bu çözümlerde koordinatör veya mesaj tabanlı yaklaşım dışında başka bir seçenek göremedim. Bütün bu süreçten edindiğim ise:

  • Bir mikroservis resilience ve bağımsız modüller ile gerçekten anlamlı oluyor
  • Bize teknolojik bağımsızlık sağlıyor
  • Kodlama kolaydır, önemli olan domain 'i ve beklentileri çok iyi analiz etmektir
  • Kullanmadan önce araçlar ve alternatifleri hakkında bilgi sahibi olmak önemle tavsiye edilir

Bu yazı ile kendi hedefimi tamamladım ve çalışan bir spring cloud mikroservis uygulamam olmuş oldu. Sıfır tecrübe ile benimle beraber buraya geldiyseniz kendinizi tebrik edin. Bu noktadan sonra mikroservislerin yönetimi gibi konular geliyor. Fark ettiyseniz bu sistemde bir değişiklik yapıldığında servisleri manuel olarak çalıştırmak ve deploy etmek zorundasınız. Bu yüzden "continuous integration" ve "continuous deployment" kavramları hayatımıza giriyor. Bu da sizi Devops denilen başka bir yolculuğa çıkarıyor. O konuda da bir gün kısa birşeyler yazabilirim. Bu yazı dizisi tamamlanmış oldu böylece. Kodu ve yazı dizisini yazma işi 2 ay sürdü. Bir sonraki yazıda görüşmek üzere :)


Bir yorum yazabilirsiniz