Event Driven Microservices

KATEGORİ

Mikroservisler, uygulamanın farklı bileşenlerini küçük servislere böler. Farklı ekiplere farklı servisleri aynı anda geliştirme imkanı verir. Her servis kendi kaynak gereksinimine göre ölçeklendirilebilir.

Spring boot ve spring cloud üzerinden ilerleyelim.

Mikroservis Tasarım Kalıpları

Externalized Configuration :

“Externalized configuration” (Harici Yapılandırma), yazılım uygulamalarının çalışma zamanında davranışlarını veya ayarlarını dışarıdan değiştirmenin bir yolunu ifade eder. Kısaca uzak bir kaynakta (örneğin github) tanımlı bulunan config dosyaları üzerinde uygulama çalışması esnasında koda müdahale olmadan değişimler yapılabilir. Uygulamanın davranışları bu şekilde değiştirilebilir. config-server-remote-settings gibi bir dizin altında servislerin konfigurasyon bilgileri tutulabilir.

API Versioning:

API sürümleme (API versioning), bir yazılım uygulamasının dışa açık API’sini (Application Programming Interface) geliştirirken, API’nin sürümünü kontrol etme ve yönetme işlemidir. API sürümleme, API’de yapılan değişikliklerin geriye dönük uyumluluk sağlamasına, kullanıcıların eski sürümlerle uyumlu kalmasına ve yeni özelliklere erişimini kolaylaştırmasına yardımcı olur. Aşağıda API sürümleme ile ilgili bazı önemli bilgiler bulunmaktadır:

  1. URL Tabanlı Sürümleme: API’nin farklı sürümlerini ayırt etmek için URL içinde sürüm numarasını kullanır. Örneğin, https://api.example.com/v1/resource veya https://api.example.com/v2/resource. Bu, farklı sürümleri açıkça tanımlar ve her sürümün ayrı bir URL ile erişilebilir olmasını sağlar.
  2. Header Tabanlı Sürümleme: API sürümünü belirlemek için HTTP başlık (header) kullanır. Genellikle “Accept” veya “Version” gibi özel başlık alanları kullanılır. Bu, URL’de sürüm numarasını göstermek yerine, sürüm bilgisini başlıkta taşır.
  3. Parametre Tabanlı Sürümleme: Sürüm bilgisini URL parametreleriyle ileten bir diğer yaklaşımdır. Örneğin, https://api.example.com/resource?v=1 veya https://api.example.com/resource?version=2. Ancak bu yöntem, URL ve başlık tabanlı sürümleme kadar yaygın değildir.
  4. Medya Türü (Media Type) Sürümleme: Sürüm bilgisini JSON veya diğer medya türleri aracılığıyla taşıyan bir yöntemdir. Özellikle JSON API’lerde yaygın olarak kullanılır.

API sürümlemenin avantajları şunlar olabilir:

  • Geriye Dönük Uyumluluk: Mevcut istemcilerin ve uygulamaların yeni sürümlerle uyumlu kalmasını sağlar.
  • API Değişikliklerinin İzlenmesi: API’nin farklı sürümleri üzerinde yapılan değişiklikleri daha iyi takip etmenizi sağlar.
  • Test ve Geliştirme Kolaylığı: Geliştirme sürecinde farklı sürümler üzerinde çalışabilirsiniz.
  • İzolasyon ve Güvenlik: Farklı sürümleri izole edebilir ve güvenlik politikalarını daha iyi uygulayabilirsiniz.

API sürümleme, API geliştirme sürecinde önemli bir konsepttir ve uygun bir şekilde uygulandığında, API’nin kullanıcıları ve sağlayıcıları için büyük avantajlar sağlayabilir.

Service Discovery: Spring Cloud ve Netflix Eureka

Service Discovery, dağıtık sistemlerin ve mikroservis mimarilerinin yönetimi için önemli bir kavramdır. Service Discovery, uygulama servislerinin otomatik olarak tespit edilmesini ve bulunmasını sağlar. Bu, ağdaki servislerin dinamik olarak eklenmesine veya kaldırılmasına olanak tanır. Service Discovery, servislerin konumlarını, IP adreslerini veya diğer kimlik bilgilerini bulma sürecini kolaylaştırır.

Netflix Eureka, Netflix tarafından geliştirilen bir Service Discovery hizmetidir. Eureka, mikro hizmet mimarilerinde kullanılan bir bileşen olarak hizmet verir. Eureka, bir uygulamanın içindeki servislerin kaydını tutar ve diğer uygulamalara bu servislerin nasıl bulunacağını sağlar. Servisler kendilerini Eureka sunucusuna kaydeder ve diğer uygulamalar bu sunucu üzerinden bu servislere erişebilirler.

Netflix Eureka’nın ana görevleri şunlar olabilir:

  1. Servis Kaydı ve Deregistrasyon: Servislerin kendilerini Eureka sunucusuna kaydetmesine ve sunucudan kaldırmasına olanak tanır.
  2. Servis Sorgulama: Diğer uygulamalar, Eureka sunucusunu kullanarak belirli bir servisin yerini sorgulayabilirler.
  3. Yük Dengeleme: Eureka, yük dengeleyici servislerle entegre edilebilir ve trafiği kayıtlı servisler arasında dengeler.

Netflix Eureka, mikro hizmet mimarilerinde dinamik servis keşfi ve yönetimi için yaygın olarak kullanılır ve özellikle Netflix tarafından geliştirilen Spring Cloud gibi çerçevelerle birleştirilerek daha fazla özellik eklenir.

API Gateway :

Diyelim ki bir e-ticaret uygulamanız var ve bir müşteri, belirli bir ürünün bilgilerini görüntülemek istiyor. Ancak bu ürünün bilgileri farklı hizmetlerde bulunuyor: biri ürünün temel bilgilerini sağlıyor, diğeri ürünün fiyatını ve stok durumunu sağlıyor ve bir başkası ürünün yorumlarını sunuyor.

  1. Bir Müşteri İstemi:Müşteri, belirli bir ürünün ayrıntılarını görüntülemek için bir istekte bulunur.
  2. Doğrudan Hizmetlere Erişim:Eğer müşteri, her hizmete doğrudan erişirse, müşteri tarafından üç farklı hizmete ayrı ayrı isteklerde bulunması gerekir. Örneğin, ürün bilgileri için bir hizmete, fiyat ve stok bilgileri için başka bir hizmete ve yorumlar için üçüncü bir hizmete istekte bulunmalıdır. Bu, müşterinin karmaşık hizmet çağrıları yapmasını gerektirir.
  3. API Gateway Kullanımı:Bunun yerine, bir API Gateway kullanabilirsiniz. Müşterinin tek bir isteği API Gateway’e gider. API Gateway, bu isteği alır, ilgili hizmetlere yönlendirir ve bu hizmetlerden gelen verileri toplayarak tek bir yanıt oluşturur. Müşteri bu şekilde sadece API Gateway ile iletişim kurar ve karmaşık hizmet çağrılarıyla uğraşmak zorunda kalmaz.

Örnek olarak:

  • Müşteri, “Ürün ID: 123” için ürün ayrıntılarını görüntülemek ister.
  • Müşterinin isteği API Gateway’e gider.
  • API Gateway, bu isteği ilgili hizmetlere yönlendirir: Ürün bilgileri hizmetine, fiyat ve stok hizmetine, yorumlar hizmetine.
  • Her hizmet ilgili bilgileri sağlar.
  • API Gateway bu bilgileri birleştirir ve tek bir yanıt oluşturur.
  • Müşteri, tek bir yanıtla ürün ayrıntılarını görüntüler.

API Gateway, istemcilerin birden fazla hizmeti nasıl çağırdığını ve bu karmaşıklığı nasıl gizlediğini anlatan bu örnekle, hizmetlerin yönetimini ve istemci-iletişimini basitleştirmeye yardımcı olur.

API Gateway kullanma gerekliliği, daha büyük ve karmaşık mikro hizmet tabanlı sistemlerde, aşağıdaki gibi bazı avantajlar sunabilir:

  1. Karmaşıklığın Merkezileştirilmesi: API Gateway, müşteri tarafından gelen isteklerin ve hizmetlere yönlendirilen isteklerin yönetimini merkezileştirir. Bu, karmaşıklığı tek bir yerde ele almanızı sağlar ve hizmetler arasında daha karmaşık iş mantığı, güvenlik kontrolleri, oturum yönetimi ve kimlik doğrulama eklemenize yardımcı olur.
  2. Yük Dengelemesi ve Hata Yönetimi: API Gateway, gelen istekleri yönlendirirken yük dengelemesi yapabilir ve hata yönetimi sağlayabilir. Hizmetlerin çoğunun yük dengelemesi ve hata işleme gibi işlevleri doğrudan ele alması gerekmeyecek şekilde hizmetlerin daha basit olmasına olanak tanır.
  3. İstemci Tarafı Performansı: İstemcilerin birden fazla hizmete yönlendirilen isteklerle uğraşması yerine tek bir isteği API Gateway’e yapmaları, istemci tarafı için daha verimli ve performanslı olabilir. API Gateway, gerektiğinde iç hizmetlere asenkron istekler yapabilir ve sonuçları birleştirebilir.
  4. API Sürüm Yönetimi: API Gateway, API sürümlerini yönetmek ve farklı sürümleri birleştirmek için kullanılabilir.

API Gateway, özellikle büyük, karmaşık ve ölçeklenebilir sistemlerde, birden fazla hizmeti yönetmek, güvenlik ve performans konularını ele almak, istemciler için daha basit bir arayüz sunmak ve servisler arası iletişimi yönetmek için kullanışlı bir araç olabilir. Ancak daha küçük projelerde veya daha basit senaryolarda, doğrudan servislerle iletişim de uygun olabilir. İhtiyaca ve projenin ölçeğine bağlı olarak doğru yaklaşım seçilmelidir.

Circuit Breaker:

Bir mikroservis çalışması durunca normalden daha yavaş cevap vermeye başlayınca beklenmeyen hatalar alınabilir, gecikmeler yaşanabilir. Bunu engellemek için her servis kontrol altında tutulmalı ve herbir request için başarısızlık senaryoları uygulanmalıdır.

Bu noktada circuit breaker devreye girer. Yapılan isteğin öylece beklemesi ya da beklenmeyen hatalar alınması yerine cache olarak tutulan ya da default bir mesaj döndürülür. Bu noktada uygun zaman aşımlarının ayarlanması ve yeniden deneme işleminin uygulanması da gereklidir. Resilience4j circuit breaker için kullanılabilecek implementasyonlardan birisidir.

Rate Limiting:

DDoS saldırılarını biliriz. Saldırgan sisteme büyük miktarda istek göndererek sistemi normal kullanıcılar için kullanılamaz hale getirir. Bu saldırılar çoğunlukla yoğun biçimde çalışan API veya servisleri hedef alır. Bu kaynağın bir saldırgan olduğunu belirten işaretlere ulaşabilirsek onun isteklerini önceden engelleyebiliriz. Rate limiting burada yardımcı olur. Bir istemci isteğine saniyede en çok 10 istek gibi bir sınır uygularsınız ve bunu gateway seviyesinde yaparsınız. Bu sınırları aşan herhangi bir istek, yönlendirme yerine gateway seviyesinde engellenecektir. Rate limiting uygulamak için in-memory key-value depolaması olan Redis’i kullanabiliriz.

Event Sourcing : Kafka

Servisler arası iletişim doğrudan rest çağrıları ile ya da event-based olabilir.

Bir servisin herhangi bir etkinlik mağazasında bir etkinlik oluşturması(produce) gibi, başka bir servis bu etkinlik mağazasına abone olur ve olayları asenkron olarak tüketir(consume). Event kullanmak istediğimiz durumlarda, bu eventleri bir yerde tutmamız gerekir ve bunun için Kafka kullanırız.

CQRS (Command Query Responsibility Segregation):

CQRS, bir yazılım uygulamasının iki temel görevini, yani veri değişikliklerini yönetme (command) ve veri okuma (query) işlemlerini, ayrı ayrı ele almayı öneren bir tasarım kalıbıdır. İşte bu kavramın gerçek hayattaki bir örneği:

Diyelim ki bir e-ticaret uygulamasını düşünün. Bu uygulama, müşterilerin ürünleri aramalarına, ürünleri görüntülemelerine, sepetlerine ürün eklemelerine ve sipariş vermek gibi birçok farklı işlem yapmalarına olanak tanır.

  1. Komut Kısmı (Command): Müşteri sipariş verdiğinde, ürün eklediğinde veya hesabını güncellediğinde, bu işlemler veri değişikliklerine neden olur. Bu veri değişiklikleri, örneğin siparişlerin kaydedilmesi, ürün stoğunun güncellenmesi veya müşteri bilgilerinin değiştirilmesi gibi komut işlemleridir.
  2. Sorgu Kısmı (Query): Müşteri, ürün aramaları yaparken veya mevcut sipariş durumlarını görüntülerken, bu işlemler yalnızca veriyi okuma amaçlıdır. Bu sorgu işlemleri, mevcut verileri okuma ve sunma görevini yerine getirir.

Neden CQRS kullanmalıyız ve nasıl işe yarıyor?

  • CQRS, komut ve sorgu işlemlerini ayrı ayrı ele almamıza olanak tanır. Bu, komut işlemleri için farklı veri modelleri veya depolama mekanizmaları kullanabiliriz. Örneğin, komut işlemleri için geleneksel bir ilişkisel veritabanı kullanabiliriz, ancak sorgu işlemleri için Elasticsearch gibi bir hızlı sorgu motorunu tercih edebiliriz.
  • Örneğin, Elasticsearch’i sorgu işlemleri için kullanırsak, bu, hızlı ve karmaşık sorguları etkili bir şekilde işleyebilir ve hızlı yanıtlar sağlayabilir. Ayrıca, bu, veri okuma işlemlerinin ölçeklenebilirliğini artırabilir.
  • CQRS, okuma ve yazma işlemlerini ayrı tutarak yazılım sistemlerini daha ölçeklenebilir hale getirebilir. Bununla birlikte, bu yaklaşım genellikle “olaysal uyumsuzluk” (eventual consistency) ile sonuçlanır, yani veri okuma işlemleri yazma işlemlerinden hemen sonra güncel bilgilere sahip olmayabilir. Ancak çoğu sistem için bu, bir sorun oluşturmaz.

CQRS yaklaşımı, komut ve sorgu işlemlerini ayrı ayrı ele alarak, belirli senaryolarda verimliliği artırabilir.

Örneğin, ürün listesi güncellendiğinde, bu güncel bilgilere anında erişim gerekmeyebilir. Doğrudan bu güncel verileri sorgulamak, sistem üzerinde gereksiz yük oluşturabilir. Bu nedenle CQRS yaklaşımıyla, güncellemeleri işleyen ve komut işlemlerini yöneten bir taraf (örneğin, ürün listesini güncelleyen taraf) ve sorguları yöneten bir taraf (örneğin, Elasticsearch gibi hızlı sorgu motorunu kullanan taraf) oluşturulabilir.

Sorgu işlemleri, hızlı bir sorgulama motorundan (Elasticsearch gibi) verileri çekerken, güncel verilere anında ihtiyaç olmayabilir. Sorgu işlemlerinin hız ve ölçeklenebilirlik açısından avantaj sağlayan bir platformdan veri çekmesi, performansı artırabilir. Bu yaklaşım, veri güncellemelerinin ve okumalarının farklı hızlarda işlendiği bir tasarım anlamına gelir, bu da “olaysal uyumsuzluk” durumuna yol açabilir, yani sorgu sonuçları hemen güncel verileri yansıtmayabilir.

Sonuç olarak, bazı senaryolarda (örneğin, hızlı sorguların önemli olduğu sorgu senaryoları) güncel verilere anında erişim zorunlu olmayabilir, bu nedenle verilerin okuma işlemleri için farklı bir platform kullanmak CQRS yaklaşımıyla uyumlu olabilir. Bu, performansı artırabilir ve sistem ölçeklendirilebilirliğini iyileştirebilir.

Authentication and authorization:

  1. Kimlik Doğrulama ve Yetkilendirme: Mikroservis ortamında birden fazla servis kullanılıyorsa, her isteğin kimlik doğrulaması yapılması ve her kaynağa yetkilendirme uygulanması önemlidir. Bu, bir isteği gönderen kullanıcının kimliğini doğrulamak ve bu kullanıcının istenen kaynaklara erişim yetkisini kontrol etmek anlamına gelir.
  2. Oauth2 Protokolü ve JWT: Oauth2 protokolü, mikroservis ortamında kimlik doğrulama ve yetkilendirme için yaygın olarak kullanılan bir standarttır. JWT, kimlik doğrulama belgelerini (token) taşımak için kullanılır ve Oauth2 ile sıkça entegre edilir. Oauth2 protokolünü JWT kimlik doğrulama belgesi türüyle kullanabiliriz. Bunun için Spring Oauth2 kaynak sunucu ve istemci bağımlılıkları kullanmamıza yardımcı olacaktır. Keycloack kullanımı da yapılabilir.
  3. OpenID Connect Protokolü: OpenID Connect protokolü, kimlik doğrulama ve yetkilendirme arasındaki ayrımı yapmak için kullanılır. Oauth, başlangıçta yetkilendirme için tasarlanmıştır ve OpenID Connect protokolü, Oauth protokolünün kimlik doğrulama eksikliğini gidermek amacıyla sonradan geliştirilmiştir.

Monitoring: Spring boot actuator, prometheus, micrometer, grafana

Mikroservislerin bellek kullanımı, saniyede gelen istek sayısı, yanıt süresi gibi metriklerinin izlenmesi önemlidir. Bu metrikler, uygulamanın performansını, sağlığını ve işleyişini değerlendirmek için kullanılır.

Spring Boot Actuator, uygulamanın çalışma zamanı metriklerini toplamak ve sunmak için kullanılır. Prometheus, bu metrikleri toplamak ve depolamak için bir sistemdir. Micrometer, farklı metrikleri toplamak ve düzenlemek için bir kütüphanedir.

Grafana, metrik verilerini görsel olarak anlayabileceğiniz bir arayüz sağlar.

Distributed tracing, log aggregation and visualization:

Daha önce de belirtildiği gibi mikro servislerle çalışırken servisler arasındaki iletişim ya rest api’ler aracılığıyla ya da bazı event source ve event driven architecture kullanılarak yapılabilir. Mikroservis mimarisinde, ayrı servisler arasında doğrudan bir ilişki olmadığı için, bir mesajın izlenmesi ve uygulamanın davranışının belirlenmesi veya belirli bir mesajla ilgili bir sorunun bulunması her zaman kolay olmayabilir. Bu durumda, bir mesajın tüm mikro servisler arasındaki yolculuğunu izleyebilmek için bir external request id uygulanabilir. Slf4J MDC ya da Spring Cloud Sleuth ve Zipkin olası çözümler. Ve tüm servislerden gelen devasa loglarla birlikte, tüm uygulama içinde bir mesajı bulmak, takip etmek veya davranışını anlamak giderek daha zor hale gelecektir. Logları merkezileştirmek ve ne olduğunuzu kolayca bulmak için görselleştirmek için ELK yığınını kullanabiliriz. Elasticsearch, Logstash, Kibana.

—–

Bir sistemde veri akışı varsa bu verileri kafka gibi bir event-store üzerinde tutabiliriz. Sonrasında veriyi event-store’dan çekecek event-driven consumer mikroservisler tanımlanır.

Veri, Kafka konuları (Kafka topics) üzerinden sürekli olarak akıtılır. Kafka, verilerin gerçek zamanlı olarak iletilmesi ve depolanması için kullanılan bir platformdır. Bu veri akışından gelen veriler, Kafka Streams gibi bir araç veya platform kullanılarak işlenir. İşlem, bu veriler üzerinde hesaplamaların yapılmasıdır. İşlenmiş veriler, tekrar başka bir Kafka konusu (Kafka topic) üzerine yazılır. Bu, sonuçları tekrar Kafka üzerinde saklamak veya başka bir uygulamanın erişebilmesi için kullanmak için yapılabilir.

Bir başka örnek şöyle olabilir;

  • İlk olarak, veri Kafka’dan tüketilir. Kafka, gerçek zamanlı veri akışını kolaylaştıran bir veri akışı platformudur.
  • Bu veriler daha sonra bir PostgreSQL veritabanına aktarılır.
  • Veritabanına aktarılan veriler üzerinde analitik hesaplamalar yapılır. Bu hesaplamalar, verilerin derinlemesine analizi, raporlama vb’dir.
  • PostgreSQL veritabanı, uzun süreli veri depolama amacıyla kullanılabilir. Bu, büyük veri miktarlarını depolamak ve daha sonra analiz veya raporlama için kullanışlıdır.

Bir başka örnek şöyle olabilir;

  • Veri kaynağından alınır, tüketilir (consume) ve elasticsearch gibi bir sorgu motoruna (query engine) gönderilir. Sorgu motorları, verilerin hızlı ve etkili bir şekilde sorgulanabilir hale getirilmesini sağlayan yazılım araçlarıdır.
  • ElasticSearch, verileri hızlı ve etkili bir şekilde sorgulamak için tasarlanmıştır.

Mikroservis mimarileri ve büyük veri akışlarıyla çalışırken veriyi Kafka’da depolama ve ElasticSearch’i hızlı ve etkili bir arama aracı olarak kullanma yaklaşımı oldukça yaygındır. Böyle bir yapıda örneğin bir mikroservis bir kafka konusunu okur, verileri analiz eder ve bir RDMS veritabanına aktarır. RDBMS, yapısal verileri saklama, karmaşık sorgular yapma ve verilerin bütünlüğünü koruma konularında güçlüdür.

Kafka bir resilient system (dirençli sistem) sağlar. Verilerin kaybını önlemek ve sistemi dirençli hale getirmek için çoğaltma faktörü(Replication Factor) adı verilen bir konsepti kullanır. Çoğaltma faktörü, her veri parçasının birden fazla kopyasını oluşturmayı ifade eder. Çoğaltma faktörü, Kafka’nın verileri farklı düğümlerde (nodes) ve farklı bölümlerde (partitions) kopyalamasını sağlar. Bu, aynı verinin birden fazla kopyasının saklandığı anlamına gelir. Kafka’nın bir düğüm veya bölümüyle ilgili bir sorunla karşılaşıldığında, bu çoğaltma faktörü sayesinde veriler hala diğer düğümlerde ve bölümlerde mevcut olur. Bu nedenle, bir düğüm veya bölüm arızalandığında, verilerin kaybı engellenir ve hizmet kesintisi minimize edilir.

Decoupling (Bağımsızlık)

Event-driven mikroservislerin ana amacı, servisleri birbirinden bağımsız hale getirmektir. Örneğin, düşünelim ki bir e-ticaret uygulamasında bir sipariş oluşturuluyor. Bu siparişi işleyen hizmet, ödeme hizmetine doğrudan çağrı yapmak yerine Kafka gibi bir araç üzerinden iletişim kurar. Bu, sipariş işleme hizmeti ve ödeme hizmeti arasındaki bağımsızlığı sağlar. Eğer ödeme servisi çökerse, sipariş işleme servisi bundan etkilenmez. Kafka gibi bir olay tabanlı sistem kullanarak iletişimi asenkron hale getiririz. Örneğin, bir kullanıcı bir mesaj gönderdiğinde, bu mesaj hemen diğer kullanıcıya iletilmez. Mesajlar olay tabanlı bir yapı üzerinde saklanır ve işlenir. Bu, kullanıcıya hemen geri bildirim yapılmasını gerektirmez ve asenkron iletişimi destekler.

Asenkron iletişim, bir hizmetin çöktüğü veya geç yanıt verdiği durumları daha iyi tolere eder. Örneğin, bir hedef servis çöktüğünde, çağıran servisi bloke etmez. Veriler biraz daha geç ulaşsa da işlem sürdürülebilir. Geleneksel REST çağrıları, HTTP protokolünü kullanarak eşzamanlıdır (senkron). Bu, veri paylaşımında doğrudan çağrılar yapmayı gerektirir. Örneğin, bir web hizmeti çağrısı bir işlemin tamamlanmasını bekler ve bu işlem çağıran servisi bloke edebilir.

Event-driven (olay tabanlı) mimari, farklı servisler arasındaki sıkı bağlantıyı azaltır. Bu, her bir servisin kendi işlevselliğini korumasını ve daha esnek ve bağımsız olmasını sağlar. Örneğin, bir ödeme servisi, sipariş servisinden bağımsızdır ve her ikisi de ayrı ayrı ölçeklenebilir.