SAGA mikroservis uygulaması - 3

Spring cloud kullanarak SAGA orkestrasyonu ile bir mikroservis projesi ayağa kaldırmaya bu yazıdan başlıyoruz. Sistemimizin omurga yapısını Eureka, Config Server ve Gateway ile kurarak çalıştıracağız. Eğer okumadıysanız alınan mimari kararları, isterlerimizi ve analizimizi serinin ilk yazısında okumanızı tavsiye ederim. Bir önceki yazıda da bu 3 teknolojinin tanıtımını yapmış ve incelemiştik. Bu yazıda 3 adet proje ayağa kaldıracağız ve mikroservisin backbone denilen yapısını oturtmuş olacağız. Mimari resmimiz her zaman olduğu gibi burada dursun.

Malzemeler

Öncelikle şu tanımlar aklımızda bulunsun. Çalışan küçük servisler hakkında bilgileri elde etmek ve load balance ile onlara yönlendirme yapmak için Eureka bileşenimiz var. Bu bileşene gerekli yönlendirmeleri bildirmek için gateway bileşenini kullanıyoruz. Config server ise yapıdaki servislerin konfigürasyonlarını ortak bir noktada toplayabilmemizi sağlıyor. Bizim tasarladığımız yapıda config server şart değildi fakat ileride projenin gelişmesi ve büyümesi durumunda konfigürasyon yönetimini kolaylaştıracaktır diye düşünerek ekledim. Buradaki 3 projemiz de Spring Boot projesi olacak ve bu yazımda bir spring boot projesini spring initializr ile nasıl oluşturabileceğiniz hakkında da bilgi vermiştim.

Eureka

Eureka için spring initializr sitesinden aşağıdaki resimdeki gibi 2 dependency 'si olan bir proje oluşturmanız gerekiyor. Eureka Server ve Spring Security. Group ve artifact isimlerini de kendinize göre belirleyebilirsiniz. Benim yaptğım "com.aldimbilet" group ve "Eureka" artifact adı şeklinde idi. Spring boot sürümünün de 2.4.0 'dan yukarı olması gerekiyor. Spring cloud 'un kendi kütüphaneleri içerisinde tutarlı çalışabilmesi adına böyle bir gereklilik varmış. Bu proje için proje çatısı bu kadar. Oluşturduğunuz bu projeyi kullandığınız IDE ile açabilirsiniz.

Açtıktan sonra pom dosyasında spring-cloud.version şeklinde cloud sürümünü göstereccektir. Buradaki sürümün 2020... veya üstü olduğundan emin olunuz. Eski spring cloud versiyonları (Hoxton veya Greenwich gibi) Eureka ve Cloud Config ile düzgün çalışamayabiliyor. Veya bazı özellikleri eksik veya hatalı olabiliyor. Özetle 2.4.0 ile uyumlu olan Spring Cloud fonksiyonları 2020... sürümleri ile geliyor diyebiliriz. Spring cloud websitesindeki "Release train Spring Boot compatibility" kısmına bakabilirsiniz.

Bu projenin bir Eureka server olarak ayağa kalkabilmesini ve Eureka arayüzüne erişilebilmesini sağlamak için Main sınıfının başına @EnableEurekaServer yazmamız yeterli oluyor. Aşağıdaki gibi görünecektir:

			
@EnableEurekaServer
@SpringBootApplication
public class AldimbiletEurekaApplication
{
	public static void main(String[] args)
	{
		SpringApplication.run(AldimbiletEurekaApplication.class, args);
	}
}
			
		

İkinci olarak da dışarıdan Eureka Server 'a erişimi kullanıcı adı ve şifle ile güvenli hale getirmek için bir security ayarı eklememiz mantıklı olacaktır. Bunu da application.properties dosyasında yapabiliyoruz. Spring Security bağımlılığını zaten eklemiştik. Kullanıcı adı şifre bilgisini aşağıdaki gibi belirtebiliriz:

			
spring.security.user.name=aldimbilet
spring.security.user.password=eureka
			
		

Yine bu properties dosyasında bu sunucuya sabit bir port vermemiz gerekiyor. Eureka için default port 8761 'dir ve burada çalışır. Fakat ben default dışında da çalıştığını görmek için default değeri değiştirmek istedim. Ayrıca uygulamaya bir isim de vermek istiyorum. İlerde bu property dosyaları çoğalacak ve bu hangisi idi diye düşünmek istemiyorum.

			
server.port=4442
spring.application.name=ab-eureka
			
		

Bu noktaya kadar spring boot projemiz için gerekli işlemler tamam. Eureka bir kayıt servisidir demiştik. Bu uygulamayı spring boot uygulaması olarak çalıştırmaya çalışırsanız kendi kendine kayıt yapmaya çalışıp "connection refused" gibi bir hata verir. Bunu engellemek için de properties dosyasında 2 özelliğimiz daha olmalı.

			
eureka.client.register-with-eureka = false
eureka.client.fetchRegistry = false
			
		

Burası da tamam olduğuna göre şu anda projenizi run yapabilirsiniz diye düşünüyorum. Kullandığınız ide 'ye göre de değişecektir fakat eclipse 'de Run as -> Spring boot application yapabiliyorsunuz. Ayağa kalktığında http://localhost:4442/ adresinden Eureka 'nın yönetim konsoluna girebilirsiniz. Ekranı biraz incelemenizi tavsiye ederim.

Ayrıca şimdiden Eureka 'da yapamız gereken bir Spring Security işlemi daha var. Projede JWT kullanacağımız için CSRF (cross site request forgery) özelliğini disable etmemiz gerekiyor. JWT kullanmayacaksanız buna gerek kalmıyor. Bir @Component sınıfı yazarak CSRF korumasını kapatıyoruz ve diğer bütün request 'leri kullanıcı adı şifre güvenliğine tabii tutuyoruz.

			
@Configuration
public class SeConfig extends WebSecurityConfigurerAdapter
{
	@Override
	protected void configure(HttpSecurity http) throws Exception
	{
		http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().httpBasic();
	}
}
			
		

Config server

Eureka tamamlandı ise sırada konfigürasyon depomuz var. Cloud config (config server isimli proje) ile konfigürasyonları (properties dosyaları) ortak bir noktada tutacağız demiştik. Bu ortak nokta bir private github repository 'si olacak. Yani github 'da bir private repository oluşturacaksınız ve Config Server kendinden ayarları isteyen servisler için ayarları gidip git 'ten çekip gelecek. Bir nevi git clone işlemine benziyor bu işlem. Ayrıca config server bu bilgileri lokal bir dosyadan da alabilirdi. Fakat veritabanı ayarları gibi ayarları github 'da tutarak hem geçmişine erişebilirsiniz hem de değişiklikleri daha hızlı dağıtabilirsiniz. Config server projesini oluşturalım aşağıdaki gibi:

Burada bağımlılıklara bakarsanız hem "config server" hem de "config client" bulunuyor. Bu sayede hem property dosyalarının sunucusu oluyor hem de kendi ayarlarını da github 'dan alabiliyor. Security 'yi yine erişimi şifre ile korumak için kullandık. Eureka Client (server değil) ile "ayağa kalktığında git erueka 'ya kayıt yaptır" emri vermiş olacağız. Buradaki en önemli dependecy de Bootstrap bağımlılığı. Spring boot 2.4.0 ile (yanlış hatırlamıyorsam) bu dependency olmadan bootstrap.properties dosyası okunmuyor.

Nedir bootstrap? Spring uygulaması ayağa kalkarken kullanılacak ve set edilecek özellikler dosyasıdır diyebiliriz. Application.properties ise uygulama ayağa kaltıktan sonra işlenebilecek özellikler oluyor. Bu projede bootstrap.properties kullanacağız. Çünkü uygulama ayağa kalkmadan github bağlantısı yapılmalı ve properties dosyasındaki ayarlar okunmalı. Diğer projelerde de bu şekilde bootstrap kullanacağız. Yine spring cloud sürümünü pom 'dan kontrol edip 2020... ve üstü olduğundan emin olunuz.

Şimdi properties dosyalarına bakalım. Application.properties 'de port verebilirim. Buradaki port default olarak 8888 'dir ve ben bunu nedense değiştiremedim. Diğer servisler otomatik olarak 8888 'e gitmeye çalıştı ve beni dinlemedi. Bu yüzden port 8888 kaldı. Bu sunucuya bağlanıp ayarların alınması için de kullanıcı adı şifre olması takdir edersiniz ki ultra önemli.

			
server.port=8888
spring.security.user.name=aldimbilet
spring.security.user.password=config
			
		

Sonra bootstrap.properties içerisinde (applicaton.properties dosyasının yanında bu dosyayı kendiniz oluşturacaksınz) git ayarlarına gelelim. Öncelikle burada bir profil ismi var. Projenizde Prod, Dev, Test gibi bir profilinizin olması her zaman faydalı olacaktır. Bu profile ismi sayesinde prod veya test veritabana bağlanmak gibi işlemler kolaylaşcaktır. Config server belirttiğiniz profilin properties dosyalarını alacaktır. Ayrıca bir de isim vermemiz gerekiyor Eureka 'da projeyi kaydedebilmek için.

			
spring.profiles.active=local
spring.application.name=ab-config-server
			
		

Bağlanılacak github repository 'si ve ayarları da aşağıdaki gibi olmalı. Buradaki "clone on start" ile config server uygulaması ayağa kalkarken güncel verileri de çekmiş oluyor. default-label önemli. Çünkü github ana branch isimlerini master 'dan main 'e çevirdi ve uygulamalar patladı. Spring boot kendini henüz güncellemedi bu konuda. Güncel bilgi :)

			
spring.cloud.config.server.git.uri=https://github.com/<username>/<reponame>
spring.cloud.config.server.git.clone-on-start=true
spring.cloud.config.server.git.default-label=main
spring.cloud.config.server.git.username=<gituser>
spring.cloud.config.server.git.password=<gitpass>
			
		

Ayrıca aşağıdaki özellikler ile bu uygulamanın da kendi konfigürasyonlarını git 'ten alabilmesini sağlayabiliyoruz. Her ne kadar kendisi config server olsa da kendi kendine istek yapması mantıksız olurdu. Bootstrap = true ile bu işlemin boot sırasında yapılacağını söylüyoruz. Fail-fast ise "eğer ayarları alamazsan bu ölümcül bir hatadır, işlemi durdurmalısın" şeklinde bildirmiş oluyoruz. Bu sayede hata fırlatıyor ve uygulama ayağa kalkmıyor.

			
spring.cloud.config.server.bootstrap=true
spring.cloud.config.discovery.service-id=ab-config-server
spring.cloud.config.fail-fast=true
			
		

Şimdi bunlar tamam da git 'teki repository nerede diyeceksiniz. Git repository 'si private olduğu için dışardan erişilmiyor. Sonuçta veritabanı bilgileri gibi önemli bilgiler var orada. Ben burada ekran görüntüsünü koyayım. Buradan ilgili repository kodlarını indirip kendi github repository 'nize atabilirsiniz. Sırası geldikçe dosyaların içeriğini de yazıyor olacağım.

Bunun gibi bir repository oluşturup bağlantı bilginizi bootstrap dosyasında güncellemelisiniz. ab-config-server-local.properties dosyasında (uygulama ismi - profil) Eureka 'ya kayıt olmak için gereken aşağıdaki property olması gerekiyor. Burada kullanıcı adı ve şifre bilgisi ile port numarasına dikkat. Eureka projesinin properties dosyasında vardı bu bilgiler.

			
eureka.client.service-url.defaultZone=http://aldimbilet:eureka@localhost:4442/eureka
			
		

En sona en önemlisini sakladım. Dependency olarak config server eklemiş olmamız otomatik olarak config server olarak ayağa kalkacağını göstermiyor. Eureka client dependency var olduğu için gidip kendisini otomatik olarak Eureka 'ya kaydeder. Fakat Config Server için @EnableConfigServer olmalı main sınıfında. Bu önemli.

			
@EnableConfigServer
@SpringBootApplication
public class AldimbiletConfigApplication
{
	public static void main(String[] args)
	{
		SpringApplication.run(AldimbiletConfigApplication.class, args);
	}
}
			
		

Bu şekilde config sunucusu da ayağa kalkabilir olmalı. Açılırken konsolda "Located property source" bilgisi ile git 'ten ayarları aldığını görebilirsiniz. Ayrıca "Registering application AB-CONFIG-SERVER with eureka" gibi bir ifade de yazacaktır. Açıldığında Eureka konsolunda görünmesi gerekiyor normal şartlarda. Bu da tamamsa bu yazının son parçası gelsin.

Gateway

Mimarimiz gereği bir de giriş kapısı eklememiz gerekiyor. Bu kapı bütün mikroservisin girişi olacak. Bazı örneklerde gateway üzerinde de load balance işlemi yapıldığını görebilirsiniz. Eureka kullanmadan direkt olarak servislere erişmeniz mümkün çünkü. Bunun için bildiğim kadarıyla ZUUL veya Gatekeeper isimli yardımcı dependency 'ler kullanılıyor. Ben biraz araştırdığımda bunların fazla esnek olmadığını görmüştüm. Property dosyalarında routing bilgileri yazdığı için örneğin belli header 'lar veya status code 'lar için belli yönlendirmeler yapmak veya request veya response içerisinde belli değerlere bakmak zor oluyormuş. Bu yüzden biraz kaçındım aslında gateway 'de bu araçlardan. Gateway için oluşturacağımız initializr projesi aşağıdaki gibi:

İlk sıradaki dependency spring cloud gateway. Bu dependency ile bu uygulamada gelen http isteklerini başka adreslere yönlendirebilecek router sınıflarını ve özelliklerini ekleyebileceğiz. Altındaki Resiliance4J ise yönlendirme yapılan servisin veya adresin ayakta olmaması veya ulaşılamaması durumunda farklı bir aksiyon alınabilmesini sağlıyor. Buna da Circut Breaker deniyor. Yani http isteğinin yolculuğunu bir devre gibi düşünürsek, devrede kesinti oluştuğunda patlamak yerine başka yere yönlendiriyor. Bunun eski alternatifi Hystrix 'ti. Eureka ile beraber kullanılıyordu ve Eureka yönlendirme yapamadığında Hystrix özellikleri devreye giriyor, sorun çıkarmak yerine hata bildirimi yapıyordu. Bunun yerine Resiliance4J ile bu özelliği gateway 'e veriyoruz ve registery 'den bağımsız bir şekilde kod yazarak daha esnek routing ayarları yapabiliyoruz. Bu projede Eureka 'ya kayıt olmak için eureka client bağımlılığını ve konfigürasyonları config server 'dan almak için config client 'ı ekledik. Bu işlemleri boot sırasında yapabilmek için de bootstrap bağımlılığı var her zamanki gibi. POM 'daki spring-cloud.version 'a da dikkat, 2020... gerekiyor minimum.

Application.properties içerisinde tek bir özelliğimiz var o da port. Gateway rastgele bir port alamayacağı için sabit bir port olması lazım tabi.

			
server.port=4441
			
		

bootstrap.properties içerisinde ise tanıdık özellikler var. Profil ismi config server 'dan (dolayısı ile github 'dan) hangi profil için ayarların çekileceğini belirtiyor. Eureka 'da tanıyabilelim diye isim de verdik.

			
spring.profiles.active=local
spring.application.name=ab-gateway
			
		

Config sunucusuna bağlanmak için bu servisin id 'sini yazıyoruz. Aslında bu olmadan da gidip default olan 8888 'e bakıyor sanırım. Fail-fast ile "ayarları alamazsan işlemi durdur ve hata ver" demiş oluyoruz. Config sunucusunda spring security ile belirlenmiş kullanıcı adı şifresini belirtmemiz gerekiyor. Aksi takdirde property 'leri çekerken spring security hatası alacaktır.

			
spring.cloud.config.discovery.service-id=ab-config-server
spring.cloud.config.fail-fast=true
spring.cloud.config.username=aldimbilet
spring.cloud.config.password=config
			
		

Ayrıca git 'teki config repository 'sinde ab-gateway-local.properties dosyası ekleyip içine Eureka bağlantı bilgisini vermelisiniz. Aksi takdirde uygulama ayağa kalkarken 8761 'de Eureka arar ve bulamaz ve hata verir ve sizi sinir eder.

			
eureka.client.service-url.defaultZone=http://aldimbilet:eureka@localhost:4442/eureka
			
		

Şimdi aslında gateway uygulama olarak ayağa kalkabilir fakat hiçbir isteği yönlendiremez. Yönlendirici için RouteLocator yazmamız gerekiyor. Bunu oluşturan kodu kalabalık yapmaması için kısalttım. Geri kalan kısmını da github 'da bulabilirsiniz. RouteCreator benim kendi yazdığım bir sınıf normalde bulamazsınız :)

			
@Autowired
RouteCreator creator;

@Bean
public RouteLocator loadBalancedRoutes(RouteLocatorBuilder builder)
{
	Function<PredicateSpec, Buildable<Route>> user_path;
	Function<PredicateSpec, Buildable<Route>> user_fail_path;
	user_path = creator.createRouterFunctionWithFallbackFilter("/user/**", "lb://ab-userservice", "user-cb", "forward:/user-failover", "user-failover");
	user_fail_path = creator.createRouterFunction("/user-failover", "lb://ab-userservice-failover");
	Builder routes = builder.routes();
	routes.route("user route", user_path);
	routes.route("user failover route", user_fail_path);
	return routes.build();
}
			
		

Burada iki adet route fonksiyonu tanımlı. Birincisi userservice 'e gidecek, ikincisi userservice-failover 'a gidecek. createRouterFunctionWithFallbackFilter fonksiyonundaki parametrelere bakalım. "/user/**" ifadesi ile /user rest endpoint 'lerini yönlendiriyoruz. Userservice 'i kodlarken bu endpoint 'te tanımlamalar yapacağız. "lb://ab-userservice" ile bu request 'i Eureka 'ya iletirken "ab-userservice" ismindeki servise iletmesini ve aynısından birden fazlası ayakta ise aralarında load balance yapmasını söylüyoruz. Buradaki isim ile userservice 'te vereceğimiz isim eşleşmek zorunda !

Sonrasında gelen "user-cb" ise userservice 'e erişememesi durumunda circuit break yaparak nereye gideceğini tanımlayacak. Burada bize Resiliance4j yardımcı oluyor. "forward:/user-failover" şeklinde bir ifade ile bu request 'in "/user-failover" isimli path 'e (service adı değil !) gitmesini söylüyoruz. Sonrasında gelen "user-failover" ise gidilecek route ismidir.

Userservice-failover için de ikinci route fonksiyonunu tanımlamıştık. createRouterFunction metodunun parametrelerine bakalım. "/user-failover" path 'ine gelen istekleri "lb://ab-userservice-failover" ile Eureka 'ya yönlendiriyoruz. ab-userservice-failover servisinde endpoint 'leri "/user-failover" ile tanımlayacağız ve servis ismini de eşleştirerek ab-userservice-failover vereceğiz.

Burada ayrıca activityservice ve paymentservice için de tanımlamalar yapılması gerekiyor. Github 'dan kopya çekebilirsiniz. Sisteme eklenen her servis gateway 'de route olarak tanımlanmış olmalı. Bunların failover servisleri varsa onlar için de ayrı route 'lar belirtilmeli. Burada en basit route tanımlarını yaptık. Daha detaylı olarak gelen request 'lerde header 'lara veya body 'lere bakarak da yönlendirme yapabilirdik. Bu gibi özelliklerin bulunduğunu da belirteyim.

Bu sınıf @Configuration anotasyonu ile bu Bean 'i oluşturuyor ve yönlendirmeler konfigüre edilmiş oluyor. Bu şekilde gateway 'de ayağa kalkıp, konfigürasyonu config server 'dan alıp Eureka 'ya kendini kaydedebilir oluyor. Konsolda "Fetching config from server at : http://localhost:8888" ve "Registering application AB-GATEWAY with eureka" ifadelerini görebilirsiniz. Eğer önce Eureka, sonra config server ve sonra da gateway 'i ayağa kaldırırsanız Eureka 'ya browser 'dan girdiğinizde gateway ve config server 'ı bağlanmış olarak görebilirsiniz. Gateway Eureka 'ya bağlanır mı derseniz, ben sistemin durumunu bilmek için bağladım derim. Bir nevi muhabir gibi kullanmış oldum Eureka 'yı. Aşağıda örnek ekran görüntüsünü koyayım. Burada servis isimlerine tıkladığınızda Actuator endpointlerine gitmeye çalışır. Actuator spring boot uygulaması hakkında bilgileri sağlayan bir kütüphanedir. Dilerseniz dependency olarak gateway ve config server 'a eklemeyi deneyebilirsiniz.

Fırından çıkan :)

Epeyce uzun oldu ama bu yazıyı bölerek bileşenler aralarındaki ilişkiyi kesmek istemedim. Bu yazıda backbone dediğimiz yapı hazır oldu. Artık üstüne sadece servislerimizi ekleyeceğiz ve bu servisleri MVC koordinatör uygulamamızdan çağıracağız. Serinin ilk yazısında bahsettiğim bazı isterlerimiz vardı. Şu anda esnek yapıda servisler yazabilmek ve kesintilerden etiklenmemek için Eureka ve Gateway 'i kullanmış olduk. Ayrıca config server ile uygulama ayarlarını ayırmış ve hantallıktan kurtarmış olduk. Eureka aynı zamanda sistemdeki yükü dağıtmaya da hazır.

Dilerseniz bu sistemin üzerinde siz istediğiniz farklı servisleri geliştirip bağlayarak çalışabilirsiniz. Routelocator 'ları yazdığınız sürece mimarimiz ve gateway 'imiz buna hazır. Bu yüzden backbone dedim bu yapıya ve ayırmadım birbirinden. Sırada userservice, activityservice ve paymentservice projeleri geliyor. Birbirlerine çok benzer kodlara sahip olacakları için hepsinin kodlarını tek tek yazmak yerine userservice üzerinde yoğunlaşacağım. Bir sonraki yazıda görüşmek üzere :)


Bir yorum yazabilirsiniz