Design Patterns
Bir önceki sayfadaki prensipleri uygulamak, çeşitli zorlukları beraberinde getirir. Bu sorunları ve çözümlerini tartışalım.
Decomposition Patterns(Ayrıştırma Kalıpları)
1. Business Yeteneğine Göre
Sorun; Mikroservisler, single responsibility prensibini uygulayarak servisleri gevşek bir şekilde birleştirmekle ilgilidir. Ancak, bir uygulamayı daha küçük parçalara bölmek mantıklı bir şekilde yapılmalıdır. Bir uygulamayı küçük servislere nasıl ayrıştırırız?
Çözüm; Bir strateji, business yeteneğine göre ayrıştırmaktır. Bir business yeteneği, bir işletmenin değer yaratmak için yaptığı bir şeydir. Belirli bir business için yetenekler kümesi, business türüne bağlıdır. Örneğin, bir sigorta şirketinin yetenekleri genellikle satış, pazarlama, sigortalama, hasar işleme, faturalama, uyumluluk vb.
2. Subdomain’e Göre
Sorun; Business yeteneklerini kullanarak bir uygulamayı ayrıştırmak iyi bir başlangıç olabilir, ancak ayrıştırması kolay olmayacak ve "God Classes" ile karşılaşacaksınız. Bu sınıflar, birden çok servis arasında ortak olacaktır. Örneğin Order sınıfı, Order Management, Order Take, Order Delivery vb. işlemlerde kullanılacaktır. Bunları nasıl ayrıştırırız?
Çözüm; "God Classes" sorunu için DDD (Domain-Driven Design) imdadına yetişiyor. Bu sorunu çözmek için subdomainler ve bounded context kavramları kullanır. DDD, kuruluş için oluşturulan tüm domaini modelini subdomainlere böler. Her subdomainin bir modeli olacaktır ve bu modelin kapsamı, bounded context olarak adlandırılacaktır. Her mikroservis, bounded context etrafında geliştirilecektir.
Subdomainleri belirlemek kolay bir iş değildir. Business’ı tamamen anlamayı gerektirir. Business yetenekleri gibi, subdomainler de business’ı ve organizasyon yapısını analiz ederek ve farklı uzmanlık alanlarını belirleyerek tanımlanır.
3. Strangler Pattern
Sorun; Şimdiye kadar bahsettiğimiz tasarım kalıpları sıfırdan uygulamalar için ayrıştırıcı uygulamalardı, ancak yaptığımız işin %80'i büyük, yekpare uygulamalarla ilgili. Yukarıdaki tüm tasarım modellerini onlara uygulamak zor olacaktır çünkü canlı olarak kullanılırken onları daha küçük parçalara ayırmak büyük bir iştir.
Çözüm; Monolitik mimariden her seferinde seçilen bir fonksiyonaliteyi kademeli bir şekilde mikroservis mimarisine dönüştürerek, zaman içinde tüm fonksiyonaliteleri monolitik mimariden mikroservis mimarisine taşımak anlamına gelen tasarım desenidir. Monolitik servisler ile mikro servisler bir süre boyunca eş yaşamlı (co-existance) bir şekilde yaşarlar ve mikro servisler zaman içerisinde stabil bir şekilde çalışmaya başlayınca monolitik uygulamadaki servisler boğulur(Strangler edilir) ve kaldırılır. Bu dönüşüm sürecinde iş ihtiyaçlarından doğan yeni talepler de mikroservis mimarisinin üzerinde ele alınmaya çalışılır.
Integration Patterns (Entegrasyon Kalıpları)
1. API Gateway Pattern
Sorun; Bir uygulama daha küçük mikroservislere bölündüğünde, ele alınması gereken birkaç endişe vardır:
- Producer bilgilerini soyutlayan birden çok mikroservise nasıl bağlanılır?
- Kullanıcı arabirimi farklı olabileceğinden, farklı kanallarda (masaüstü, mobil ve tabletler gibi) uygulamalar aynı backend servisine yanıt vermek için farklı verilere ihtiyaç duyar.
- Farklı tüketiciler, yeniden kullanılabilir mikroservislerden gelen yanıtların farklı bir biçimine ihtiyaç duyabilir. Veri dönüşümünü veya field manipülasyonunu kim yapacak?
- Producer mikroservisi tarafından desteklenmeyebilecek farklı türde Protokollerin nasıl ele alınacağı konusu.
Çözüm; Bir API Gateway, yukarıdakilerle sınırlı olmamak üzere, mikroservis uygulamasının ortaya çıkardığı birçok endişeyi gidermeye yardımcı olur.
- API Gateway, herhangi bir mikroservis çağrısı için tek giriş noktasıdır.
- Producer ayrıntılarını soyutlayarak ilgili mikroservise bir istek yönlendirmek için bir proxy hizmeti olarak çalışabilir.
- Bir talebi birden çok hizmete dağıtabilir ve tüketiciye geri göndermek için sonuçları toplayabilir.
- Ayrıca, protokol talebini (ör. AMQP) başka bir protokole (ör. HTTP) veya tersine de dönüştürebilir
- Mikroservisin kimlik doğrulama/yetkilendirme sorumluluğunu da üstlenebilir.
2. Aggregator Pattern
Sorun; API Gateway tasarım kalıbında veri toplama sorununu çözmekten bahsetmiştik. Ancak burada bütüncül olarak bahsedeceğiz. Business işlevselliğini birkaç küçük mantıksal kod parçasına bölerken, her servis tarafından döndürülen verilerle nasıl işbirliği yapılacağını düşünmek gerekli hale gelir. Bu sorumluluk tüketiciye bırakılamaz, çünkü o zaman üretici uygulamasının internal implementasonu anlaması gerekebilir.
Çözüm; Aggregator kalıbı, bunun ele alınmasına yardımcı olur. Farklı servislerden gelen verileri nasıl toplayabileceğimizi ve ardından nihai yanıtı tüketiciye nasıl gönderebileceğimizi anlatıyor. Bu iki şekilde yapılabilir:
- Composite bir mikroservis, gerekli tüm mikroservislere çağrı yapacak, verileri birleştirecek ve geri göndermeden önce verileri dönüştürecektir.
- Bir API Gateway talebi birden çok mikroservise bölebilir ve verileri tüketiciye göndermeden önce toplayabilir.
Herhangi bir iş mantığı uygulanacaksa, bir composite mikroservis seçilmesi önerilir. Aksi takdirde, API Gateway genel çözümdür.
3. Client-Side UI Composition Pattern
Sorun; Servisler, business yeteneklerinin/subdomainlerinin ayrıştırılmasıyla geliştirildiğinde, kullanıcı deneyiminden sorumlu servislerin birkaç mikroservisten veri çekmesi gerekir. Monolitik mimaride, tüm verileri almak ve UI sayfasını yenilemek/göndermek için UI’dan bir backend servisine yalnızca bir çağrı yapılırdı. Ancak artık eskisi gibi olmayacak. Bunu nasıl yapacağımızı anlamamız gerekiyor.
Çözüm; Mikroservislerde, UI’ın ekranın/sayfanın birden çok bölümü/bölgesi ile bir iskelet olarak tasarlanması gerekir. Her bölüm, verileri çekmek için ayrı bir backend mikroservisine çağrı yapacaktır. Buna, servise özgü UI componentleri oluşturmak denir. AngularJS ve ReactJS gibi frameworkler bunu kolayca yapmanıza yardımcı olur. Bu ekranlar Single Page Applications(SPA) olarak bilinir. Bu, uygulamanın tüm sayfa yerine ekranın belirli bir bölgesini yenilemesini sağlar.
Database Patterns
1. Database per Service
Sorun; Mikroservisler için veritabanı mimarisinin nasıl tanımlanacağı konusunda bir sorun var. Ele alınması gereken endişeler şunlardır:
- Servisler gevşek bir şekilde birleştirilmelidir. Bağımsız olarak geliştirilebilir, dağıtılabilir ve ölçeklendirilebilirler.
- Bazı business işlemlerinin birden çok servise ait verileri sorgulaması gerekir.
- Ölçeklendirmek için veritabanlarının bazen çoğaltılması ve parçalanması gerekir.
- Farklı servislerin farklı veri depolama gereksinimleri vardır.
Çözüm; Yukarıdaki endişeleri çözmek için, mikroservis başına bir veritabanı tasarlanmalıdır; yalnızca o servise özel olmalıdır. Yalnızca mikroservis API'si tarafından erişilmelidir. Diğer servisler tarafından doğrudan erişilemez olmalıdır. Örneğin, ilişkisel veritabanları için servis başına özel tablolar, servis başına schema veya servis başına veritabanı sunucusu kullanabiliriz. Her mikroservisin ayrı bir veritabanı kimliği olmalıdır, böylece bir bariyer oluşturmak ve diğer servis tablolarını kullanmasını önlemek için ayrı erişim verilebilir.
2. Shared Database per Service
Sorun; Mikroservisler için ideal olan servis başına bir veritabanından bahsetmiştik, ancak bu, uygulama sıfırdan geliştirildiğinde ve DDD ile geliştirildiğinde mümkündür. Uygulama monolit bir uygulama ise ve mikroservis mimariye geçirilmeye çalışılıyorsa, denormalizasyon o kadar kolay olmaz. Bu durumda uygun mimari nedir?
Çözüm; Bu tarz bir dönüşümde uygulamayı daha küçük mantıksal parçalara bölmek iyi bir başlangıçtır. Fakat sıfırdan geliştirilen uygulamalar için uygulanmamalıdır. Bu modelde, bir veritabanı birden fazla mikroservise bölünebilir, ancak maksimum 2-3 ile sınırlandırılmalıdır, aksi takdirde ölçekleme, otonomluk ve bağımsızlığın sağlanması zor olacaktır.
3. Command Query Responsibility Segregation (CQRS)
Sorun; Servis başına veritabanını uyguladığımızda, birden fazla servisten ortak veri gerektiren bir sorgulama gereksinimi vardır -bu mümkün değildir. O halde, mikroservis mimarisinde sorguları nasıl uygularız?
Çözüm; CQRS, ana odağı write (yazma) ve read (okuma) sorumluluklarının ayrıştırılmasına dayanan bir mimari tasarım modelidir. CQRS mimarisi, CQS ilkesi baz alınarak kurulmuştur. CQS’in ana fikrinden bahsetmek gerekirse; bir metot objenin durumunu değiştirmelidir ya da geriye bir sonuç dönmelidir, ancak 2 işlemi birden yapmamalıdır.
4. Saga Pattern
Sorun; Her servisin kendi veritabanı olduğunda ve bir business logic birden fazla servisi kapsadığında, servisler arasında veri tutarlılığını(consistency) nasıl sağlarız? Örneğin, müşterilerin kredi limitinin olduğu bir e-ticaret uygulamasında, yeni siparişin müşterinin kredi limitini aşmaması uygulama tarafından sağlanmalıdır. Siparişler ve Müşteriler farklı veritabanlarında olduğundan, uygulama yerel bir ACID işlemini basitçe kullanamaz.
Çözüm; Saga Pattern ile oluşturulan sistemlerde gelen istek ile daha sonraki her adım, bir önceki adımın başarılı şekilde tamamlanması sonrasında tetiklenir. Herhangi bir failover durumunda işlemi geri alma veya bir düzeltme aksiyonu almayı sağlayan pattern’dir.
- Orchestration(Orkestrasyon) — Bu yaklaşımda tüm işlemleri yöneten bir Saga orchestrator ’u vardır. Bu orchestrator subscribe olan tüm consumer’lara ne zaman ne yapacağını ileten bir consumer’dır.
- Choreography(Koreografi) — Bu yaklaşımda merkezi bir yönetici yoktur. Her servis işlemi tamamlar ve event fırlatır ve bir sonraki servis ilgili event’i consume edip sürecine devam eder.
Observability Patterns (Gözlemlenebilirlik Kalıpları)
1. Log Aggregation
Sorun; Bir uygulamanın birden çok makinede çalışan birden çok servis instance’ından oluştuğu bir kullanım durumu düşünün. İstekler genellikle birden çok servis instance’ını kapsar. Her servis instance’ı, standartlaştırılmış bir biçimde bir log dosyası oluşturur. Belirli bir istek için loglar aracılığıyla uygulama davranışını nasıl anlayabiliriz?
Çözüm; Her servis instance’ından logları toplayan merkezi bir log servisine ihtiyacımız var. Kullanıcılar logları arayabilir ve analiz edebilir.
2. Performance Metrics
Sorun; Mikroservis mimarisi nedeniyle servis sayısı arttığında, bir sorun olduğunda uyarıların gönderilebilmesi için işlemlerin izlenmesi kritik hale gelir. Uygulama performansını izlemek için ölçümleri nasıl toplamalıyız?
Çözüm; Bireysel işlemler hakkında istatistik toplamak için bir metric servisi gereklidir. Raporlama ve uyarı sağlayan bir uygulama servisinin metriclerini toplamalıdır. Metricleri toplamak için iki model vardır:
- Push — Servis, metricleri metric servisine gönderir, örn. NewRelic, AppDynamics
- Pull — Metric servisleri, metricleri servisten çeker; Prometheus
3. Distributed Tracing
Sorun; Mikroservis mimarisinde, istekler genellikle birden çok servisi kapsar. Her servis, birden çok serviste bir veya daha fazla işlem gerçekleştirerek bir isteği işler. O halde, sorunu gidermek için uçtan uca bir isteği nasıl izleriz?
Çözüm;
- Her external isteğe benzersiz bir external request id atayan,
- External reques id’yi tüm servislere ileten,
- Tüm log mesajlarına external request id’yi dahil eden,
- Merkezi bir serviste external bir istek işlenirken gerçekleştirilen istekler ve işlemler hakkındaki bilgileri (örn. başlangıç zamanı, bitiş zamanı) kaydeden, bir servise ihtiyaç vardır.
Spring Cloud Slueth, Zipkin server birlikte yaygın kullanımdır.
4. Health Check
Sorun; Mikroservis mimarisi uygulandığında, bir servisin çalışır durumda olması ancak işlemleri gerçekleştirememesi olasılığı vardır. Bu durumda, bir isteğin bu başarısız instance’lara gitmemesini nasıl sağlarsınız? Bir load balancer modeli uygulamasıyla.
Çözüm; Her servisin, uygulamanın durumunu kontrol etmek için kullanılabilecek /health gibi bir endpointi olması gerekir. Bu API, ana bilgisayarın durumunu, diğer servislere/altyapıya bağlantıyı ve herhangi bir özel logic’i kontrol etmelidir.
Cross-Cutting Concern Patterns
1. External Configuration
Sorun; Bir servis tipik olarak diğer servisleri ve veritabanlarını da çağırır. Dev, QA, UAT, prod gibi her ortam için endpoint URL'si veya bazı yapılandırma özellikleri farklı olabilir. Bu özelliklerden herhangi birinde yapılacak bir değişiklik, servisin yeniden oluşturulmasını ve yeniden deploy edilmesini gerektirebilir. Konfigürasyon değişiklikleri için kod değişikliğinden nasıl kaçınırız?
Çözüm; Endpoint URL'leri ve kimlik bilgileri dahil olmak üzere tüm yapılandırmayı dışa aktarın. Uygulama bunları başlangıçta veya anında yüklemelidir.
2. Service Discovery Pattern
Sorun; Mikroservis mimaride, servisleri çağırma açısından birkaç konuyu ele almamız gerekir:
- Container teknolojisi ile IP adresleri, servis instancelarına dinamik olarak tahsis edilir. Adres her değiştiğinde, bir tüketici hizmeti bozulabilir ve manuel değişiklik gerektirebilir.
- Her servis URL'sinin tüketici tarafından hatırlanması ve sıkı bir şekilde birleştirilmesi gerekir.
Peki, tüketici veya router mevcut tüm service instancelarının ve konumlarını nasıl biliyor?
Çözüm; Her producer servisinin metadatalarını tutacak bir servis registry oluşturulmalıdır. Bir servis instance’ı başlatılırken registry’e kaydolmalı ve kapatılırken kaydı kaldırmalıdır. Tüketici veya router, registry’i sorgulamalı ve servisin yerini bulmalıdır. Registry’nin ayrıca, servislerin yalnızca çalışan instancelarını bu servis aracılığıyla tüketilebilmesini sağlamak için producer servis üzerinde bir healt-check yapması gerekir. İki tür service discovery vardır: client side ve server side. Client side discovery örneği Netflix Eureka'dır ve server side discovery örneği AWS ALB'dir.
3. Circuit Breaker Pattern
Sorun; Bir servis genellikle verileri almak için diğer servisleri arar ve downstream servisinin çalışmaması ihtimali vardır. Bununla ilgili iki sorun var: Birincisi, istek sürekli olarak hizmet dışı kalacak, ağ kaynaklarını tüketecek ve performansı yavaşlatacaktır. İkincisi, kullanıcı deneyimi kötü ve öngörülemez olacaktır. Art arda gelen hizmet hatalarından nasıl kaçınırız ve hataları incelikle nasıl ele alırız?
Çözüm; Circuit Breaker Pattern, bir yazılımda hataları tespit ederek hatanın tekrar etmemesini sağlar. Böylece sistemde hataların tekrar etmesi sonucu oluşacak bloklanma, hizmet kesintileri, aşırı kaynak kullanımı gibi sorunlarla karşılaşılmasını önleyerek, sistemin sağlıklı çalışmasını amaçlar.
4. Blue-Green Deployment Pattern
Sorun; Mikroservis mimarisi ile bir uygulama birçok mikroservise sahip olabilir. Tüm servisleri durdurur ve ardından bir üst sürümü deploy edersek, kesinti süresi çok büyük olur ve işletmeyi etkileyebilir. Ayrıca, rollback katlanılmaz bir çile haline gelir. Deployment sırasında servislerin kapalı kalma sürelerini nasıl önleyebilir veya azaltabiliriz?
Çözüm; Blue-Green Deployment Pattern, kapalı kalma süresini azaltmak veya ortadan kaldırmak için uygulanabilir. Bunu, Blue ve Green olmak üzere birbirinin aynı iki production ortamını çalıştırarak başarır. Green'in mevcut canlı örnek olduğunu ve Blue'nun uygulamanın yeni sürümü olduğunu varsayalım. Herhangi bir zamanda, tüm production trafiğine hizmet veren, ortamlardan yalnızca biridir. Tüm bulut platformları, blue-green bir deploymentın uygulanması için seçenekler sunar. Bu konuyla ilgili daha fazla ayrıntı için bu makaleye göz atın.
Sidecar, Chained Microservice, Branch Microservice, Event Sourcing Pattern, Continuous Delivery Patterns ve daha fazlası gibi mikroservis mimarisiyle kullanılan birçok başka model vardır. Mikroservislerle ilgili daha fazla deneyim kazandıkça liste büyümeye devam ediyor. İlerleyen zamanlarda bunları da bu dökümana ekleyeceğim…