Mikroservis Mimarisinde Servislerin Sınırlarını Belirlemek ve Geçiş Stratejileri
Monolitik uygulamanızı mikroservislere bölmeye karar verdiniz ve uygulamayı karşınıza aldınız? İlk olarak nereden başlarsınız? Nasıl bir yol izlersiniz? Aslında bunların hepsinin cevabı sizin uygulamanızda, businessınızda ve içinde bulunduğunuz şirket kültüründe saklı ancak yine de bir kaç temel prensip ve izleyecek yoldan bahsetmek istiyorum.
Monolitik uygulamalardaki sorun genellikle, cohesion ve coupling eksikliğidir. Birbiriyle alakasız kodlar bir aradadır. Yani low cohesion bir yapıdadır. Moduller de birbirlerine sıkı sıkıya bağlıdır. Bir yerde bir değişiklik yapmak istediğinizde zincirleme şekilde başka başka yerlere de dokunmanız gerekir bu da uygulamanın loose coupling olmamasına işaret eder. Normal şartlar altında uygulamada bir satır kod değiştirmek istediğinizde, bunu kolayca yapabilmelisiniz ancak legacy monolitik uygulamalarda bunu kodun gerisini etkilemeden yapamazsınız çünkü uygulamada domainler ve boundryler açıkça belli değildir. Ayrıca tüm sistemi baştan deploy etmeniz gerekir.
Nasıl bir başlangıç yapmalıyız?
Monolitik bir uygulamayı ufak parçalara ayırmayı istemek akıllıca bir başlangıç. Mikroservis mimarisi de zaten bize monolitik yapıdan gelen kısıtları çözmeyi vaad eder. Ancak burada incremental yaklaşımı takip etmek mikroservisleri keşfede keşfede gitmek yerinde bir davranış olacaktır. Diyelim ki büyük bir uygulamanız, domaininiz var. Tek seferde büyük bir mikroservis geçiş haritası çıkarıp bunu da duvara asıp 4 koldan buna girişmek yerine, uygulamanın yalnızca ufak bir parçasını seçip orası üzerine plan yapıp mikroservisler oluşturup her bir oluşturduğunuz mikroserviste neler doğru neler yanlış nerelerde hata yapılmış gözlemleyip ilerleyen iterasyonlarda bunları gidererek devam etmek yerinde olacaktır. Eğer ki bir yerde bir şeyleri yanlış yaptığınızı fark ederseniz bu strateji bunun etkisini küçük tutacak ve ilerleyen süreci kurtaracaktır.
Peki nereden başlamalıyız?
Yine monolitik uygulamamızı göz önüne alalım. İçerisinden bir parça koparıp ilk mikroservisimizi yazacağız. Ama hangisi? Biraz araştırma yaptığınızda burada iki ayrı fikir olduğunu görebilirsiniz. Bir kısım, uygulamanız içerisindeki en düşük riskli bounded contexti (bounded contextler net değilse öncesinde bir refactor gerekecektir.) alıp geçirmeli derken bir diğer kısımda size katma değeri en yüksek olacak kritik kısmı geçirmelisiniz der.
Burada şahsi fikrim, sisteme baktığınızda ilk olarak mikroservise geçirmenin size en fazla katma değer sağlayacağı kısmı belirlemek ve orası üzerine çalışmak olacaktır. Örnek vermek gerekirse, bir e ticaret şirketi için uygulama içerisindeki Product domainine özgü kısımlar ayrıştırılırsa tüm şirket ve neredeyse tüm uygulamalar bundan istifade edebilir ve katma değeri yüksek bir iş olur. Bunun yanında eğer ki bu domain en çok user trafiğine sahip domain ise deployment time, scalibility, development speed vs gibi mikroservis katkılarını net bir şekilde görüp sunabilirsiniz.
Birkaç önemli nokta
Mikroservis mimarisine geçmeye karar verdikten sonraki en önemli aşamalardan biri de monolitik uygulamalarımızı mikroservislere dağıtırken o mikroservislerin sınırlarının belirlenmesidir. Bu konu ile ilgili çok fazla fikir ve ölçüm kriteri olmasıyla birlikte, ben object oriented conceptte çokça kullanılan, buna ilaveten mikroservis mimarisinde de oldukça önemli olan iki key konsept ile başlamak istiyorum. Coupling ve Cohesion.
Loose Coupling
Aslında loose coupling kavramı bir varlığın bir diğerine gevşek bir şekilde bağlı olması yani o varlıkta yapılan değişikliğin diğer varlıkta bir değişiklik gerektirmemesi gibi düşünülebilir. Bu varlık class, module, servis vs olabilir. Dolayısıyla servisler loose couple olarak bağlı olduğu zaman, bir servisteki yapılan değişim diğer serviste de değişiklik yapmayı gerektirmemelidir. Yani bir serviste değişlik yapıp deploy etme süreciniz diğer bir serviste ya da sistemin geri kalanında değişiklik gerektiriyorsa sizin sisteminizde birbirine sıkı sıkıya bağlı yani tightly coupledır. Burada anahtar nokta loosely coupled bir servis iş birliği yaptığı diğer servisler hakkında olabildiğince az şey bilmeli.
High Cohesion
Cohesion kavramı yapışkanlık demektir. Birbiriyle ilişkili olan fonksiyonellikler, propertyler birbirine olabildiğince yakın yani yapışkan olmalıdır. Bu kavram yine class, module, servis gibi konseptlere uyarlanabilir. Örneğin bir class high cohesiona sahip ise o classtaki tüm fonksiyonellikler aynı ana konsepte hizmet eder. Basit bir örnekle Product classındaki tüm fonksiyonlar net olarak productın yetkinliklerini yansıtır. Genel olarak birbirine benzer davranışları birlikte, ilişkisi olmayan davranışları da başka yerde tutmamız gerekir. Sebebi ise bir konsepte ait fonksiyonelliği değiştirmek istediğimizde bunun yeri belli olmalıdır. Tek yerde değişikliği yapıp deploy edebilmeliyiz. Eğer ki bir fonksiyonelliği değiştirmek için bu fonksiyonelliği farklı farklı yerlerde değiştirmemiz gerekiyorsa, cohesion yani yapışıklılık kavramı düşük demektir. Bunun sonucunda da kod geliştirmesi daha yavaş ve daha riskli olacaktır.
Domain ve Bounded Contextler
Domaini mikroservislere ayırabilmek için öncelikle o domaini iyi bir şekilde biliyor olmak gerekir. Yeni bir domaindeyseniz, domaininizi mikroservislere bölmek için acele etmeseniz iyi olur. Domaini iyi anlamadan, bounded contextleri iyi belirlemeden çıkılan mikroservis yolcuğu ileride çok daha büyük bir yük getirebilir. Başlangıçta düşündüğümüz use caseler beklediğimizden çok farklı çıkabilir.
Domain boundrylerimizi belirlerken de ilişkili davranışları bir arada tutmamız (high cohesion) ve diğer boundryler ile gevşek bağlı(loosley coupled) bir iletişim kurmamız gerekir. Bu aşamada net belli olmayan monolitik uygulamalarda boundryleri netleştirmek için bir refactor gerekecektir. Domaininizdeki bounded contextleri bulduğunuz zaman doğru yoldasınız demektir çünkü domaindeki her bir bounded context mikroservis olmak için iyi bir adaydır. Aslında bounded contextler üzerine düşünürken, paylaşılan bir data olarak değil de bunu domainin kalanına sağlayacak capabilityleri olan contextler olarak düşünmeliyiz.
İlk denemeler
Başka bounded contextleri oluştururken ilk seferde mükemmel well defined contextler oluşturmayı bekleyemeyiz. Öncelikli olarak coarse-grained bounded contextler oluşturduğumuzu göreceğiz. Bu coarse-grained bounded contextler aslında kendi altlarında da contextler barındırıyor olacaklar. Sonrasında bunları daha da küçük bounded contextlere ayırabiliriz. Ancak ayırmalı mıyız? Aslında burada kesin bir kural olamamakla birlikte, eğer ki bu ana coarse contextin altındaki sub nested contextlerin her birinden farklı ekip sorumlu olacaksa ayırabiliriz. Ya da testi kolay olsun diye ayrıştırma yapabiliriz.
microservices.io ‘ da aslında anlattıklarımızdan farklı bir şey söylememekle beraber monolitik bir uygulamayı mikroservislere bölmek için izlenmesi gereken yolu 5 stepte toplamış.
Step 1: Split the code
Step 2: Split the database
Step 3: Define a standalone service
Step 4: Use the standalone service
Step 5: Remove the delivery management functionality
Step 1 – Split the code : Bu aşamada monolitik uygulamanızda mikroservise çevirmek istediğiniz kısmı refactor edip, diğer kısımlardan ayırıp bounded context haline getirmeniz gerekiyor. Örneğin aşağıdaki görselde Order and Delivery Management monolitik bir uygulamada high coupled bir şekilde bulunmakta ve mikroservis geçişi için delivery managementi buradan çekip ayrı bir contexte çekilmiş.
Step 2: Split the database : Bu aşamada ise ayrı bounded contexte ayırdığımız delivery managementın databaseini ayırıyoruz. Ancak halen delivery management aynı monolitik appin içerisinde. Databaselerin ayrıştılırması detaylı bir konu olup, konuyla ilgili detaylı bilgiye bu konuya değindiğim yazım üzerinden ulaşabilirsiniz.
Step 3: Define a standalone Service : Bu aşama ise artık elimizdeki delivery contexti için bir servis yani mikroservisimizi oluşturduğumuz aşama. Burada ilgili delivery servisi ayrıştırdığımız database’e bağlayarak production testlerini servisi test edebiliriz. Ancak servis hala user kullanımına açık değildir.
Step 4: Use the standalone service : 4. step ise artık oluşturduğumuz mikroservisi kullanıma aldığımız kısım. Ancak ilk kullanıma alım sırasında monolitik app içerisindeki delivery modülü hala duruyor. Smooth bir geçiş için monolitik app ve servis yükü api gateway üzerinden paylaşıyor.
Step 5: Remove the delivery management functionality : Artık herşey okeyse monolitik appteki delivery servisi çat diye silip uygulamayı ferahlatabilir ve nefes aldırabiliriz. 🙂