Bir yazılım sistemi büyüdükçe, onu tek bir bütün olarak yönetmek giderek zorlaşır. Küçük bir değişiklik yapmak için tüm sistemi yeniden derlemek, bir bölümdeki hatanın tüm uygulamayı etkilemesi veya farklı ekiplerin aynı kod tabanı üzerinde birbirini engellemesi, büyük ölçekli yazılım projelerinde sıkça karşılaşılan sorunlardır. Mikroservis mimarisi, bu sorunlara bir yanıt olarak ortaya çıkan bir yazılım tasarım yaklaşımıdır.
Mikroservis mimarisinde bir uygulama, her biri kendi başına geliştirilip dağıtılabilen, belirli bir iş yeteneğine odaklanan küçük ve bağımsız servislere bölünür. Bu servisler birbirleriyle genellikle ağ üzerinden, hafif iletişim protokolleri aracılığıyla haberleşir. Bu yaklaşımın karşıtı olarak konumlandırılan "monolitik mimari"de ise tüm uygulama tek bir kod tabanı ve tek bir dağıtım birimi olarak geliştirilir; bu iki yaklaşım arasındaki tercih, günümüz yazılım mimarisi tartışmalarının en sık gündeme gelen konularından biridir.
Bu yazıda mikroservis mimarisinin nereden geldiğini, hangi temel prensiplere dayandığını ve hangi durumlarda tercih edilmesi gerektiğini ele alıyoruz.
Mikroservislerin popülerliği, aynı zamanda beraberinde bir tartışmayı da getirdi: bu mimari her proje için doğru çözüm müdür, yoksa belirli ölçek ve organizasyon yapılarına özgü bir çözüm müdür? Bu soruyu yanıtlayabilmek için, öncelikle bu yaklaşımın hangi sorunlara karşı geliştirildiğini anlamak gerekir.
Mikroservis mimarisinin kökleri, 2000'li yılların başında yaygınlaşan servis odaklı mimari (Service-Oriented Architecture, SOA) yaklaşımına dayanır. SOA, büyük sistemlerin birbiriyle standart protokoller üzerinden haberleşen servislere bölünmesi fikrini savunuyordu; ancak bu dönemdeki uygulamalar genellikle ağır kurumsal servis veri yolları (enterprise service bus) ve karmaşık standartlara dayanıyordu.
2000'li yılların ortalarında bazı büyük teknoloji şirketlerinin kendi iç sistemlerini bağımsız servislere ayırdığı bilinmektedir; bu yaklaşımın amacı, farklı ekiplerin birbirinden bağımsız biçimde geliştirme yapabilmesini ve sistemin belirli bölümlerinin ayrı ayrı ölçeklenebilmesini sağlamaktı. 2009 yılından itibaren Netflix'in, hızla büyüyen kullanıcı tabanına hizmet verebilmek için monolitik yapısını kademeli olarak bağımsız servislere ayırdığı ve bu süreci kendi teknoloji blogunda ayrıntılı biçimde paylaştığı bilinmektedir; bu deneyim, sektörde mikroservis yaklaşımının somut bir başarı örneği olarak sıkça referans gösterilir.
"Mikroservis" teriminin bugünkü anlamıyla yaygınlaşmasında, yazılım mimarisi alanında tanınan James Lewis ve Martin Fowler'ın 2014 yılında yayımladığı makalenin önemli bir etkisi olmuştur. Bu makale, mikroservis mimarisinin temel prensiplerini derli toplu biçimde tanımlayarak, kavramın sektörde daha geniş bir kitle tarafından tartışılmasının önünü açtı.
Bu dönemde mikroservislere geçişi teşvik eden bir diğer faktör de organizasyonel ölçekti. Şirketler büyüdükçe, tek bir kod tabanı üzerinde çalışan onlarca hatta yüzlerce geliştiricinin birbirini engellemeden çalışabilmesi giderek zorlaşıyordu. Mikroservis mimarisi, her ekibin kendi servisinden sorumlu olduğu ve diğer ekiplerin iç detaylarıyla ilgilenmek zorunda kalmadığı bir organizasyon modeliyle de doğal biçimde örtüşüyordu. Bu nedenle mikroservislerin yükselişi, yalnızca teknik bir tercih değil, aynı zamanda büyüyen mühendislik organizasyonlarının kendini yeniden yapılandırma ihtiyacının da bir yansıması olarak okunabilir.
Her mikroservis, ideal olarak tek bir iş yeteneğine odaklanır; örneğin bir e-ticaret sisteminde ödeme işlemleri, stok yönetimi ve kullanıcı hesapları ayrı servisler olarak tasarlanabilir. Bu servislerin her biri, diğerlerinden bağımsız olarak geliştirilip dağıtılabilir; bu da bir servisteki değişikliğin tüm sistemin yeniden dağıtılmasını gerektirmemesi anlamına gelir.
Mikroservisler genellikle HTTP tabanlı REST API'ler veya mesaj kuyrukları aracılığıyla birbirleriyle haberleşir. Bu iletişimin ağ üzerinden gerçekleşmesi, monolitik bir uygulamadaki fonksiyon çağrılarına kıyasla ek bir gecikme ve karmaşıklık kaynağı oluşturur; ağ bağlantısı her zaman güvenilir olmayabilir, bu nedenle servisler arası iletişimin hata senaryoları da dâhil olmak üzere dikkatle tasarlanması gerekir.
Geleneksel monolitik uygulamalarda genellikle tek bir paylaşılan veri tabanı kullanılırken, mikroservis mimarisinde her servisin kendi verisini bağımsız biçimde yönetmesi tavsiye edilir. Bu yaklaşım servisler arasındaki bağımlılığı azaltsa da, farklı servislerdeki verilerin tutarlılığının nasıl sağlanacağı gibi yeni sorular ortaya çıkarır; tek bir işlemin birden fazla servisi ilgilendirdiği durumlarda, geleneksel veri tabanı işlemlerinin sunduğu kesin tutarlılık yerine, sistemin belirli bir gecikmeyle tutarlı hale geldiği "nihai tutarlılık" (eventual consistency) modelleri sıklıkla tercih edilir.
Mikroservis mimarisinin önemli avantajlarından biri, yalnızca yoğun talep gören servislerin ayrı ayrı ölçeklenebilmesidir. Örneğin bir e-ticaret sisteminde yoğun kampanya dönemlerinde yalnızca sipariş servisinin ek kaynaklarla desteklenmesi, tüm sistemin ölçeklenmesine kıyasla çok daha verimli bir yaklaşımdır.
Mikroservis mimarisinde bir servisteki hatanın diğer servisleri etkilememesi hedeflenir. Bu izolasyon, iyi tasarlanmış bir sistemde bir servisin geçici olarak yanıt vermemesi durumunda dahi diğer servislerin çalışmaya devam edebilmesini sağlar. Ancak bu izolasyonun gerçekten sağlanabilmesi için, devre kesici (circuit breaker) desenleri ve zaman aşımı (timeout) politikaları gibi ek mühendislik önlemlerinin bilinçli biçimde uygulanması gerekir; aksi takdirde tek bir servisteki yavaşlama, zincirleme biçimde tüm sistemi etkileyebilir.
Bir monolitik uygulamada bir hatayı ayıklamak genellikle tek bir kod tabanında adım adım ilerlemeyi gerektirirken, mikroservis mimarisinde bir kullanıcı isteği onlarca servisten geçebilir; bu da bir hatanın hangi serviste ve hangi aşamada ortaya çıktığını tespit etmeyi önemli ölçüde zorlaştırır. Bu zorluk, dağıtık izleme (distributed tracing) araçlarının mikroservis mimarisinin neredeyse zorunlu bir tamamlayıcısı hâline gelmesine yol açmıştır; bu araçlar, bir isteğin sistem içindeki tüm yolculuğunu uçtan uca izleyerek, gecikmenin veya hatanın tam olarak hangi serviste oluştuğunu görselleştirir. Benzer biçimde, entegrasyon testlerinin de birden fazla servisi bir arada çalıştırmayı gerektirmesi, mikroservis mimarisinde test stratejilerinin monolitik uygulamalara kıyasla daha katmanlı ve daha dikkatli tasarlanmasını gerektirir.
2013 yılında tanıtılan Docker konteynerleştirme teknolojisi, bir uygulamanın ve tüm bağımlılıklarının taşınabilir bir paket içinde çalıştırılabilmesini sağlayarak mikroservislerin dağıtımını önemli ölçüde kolaylaştırdı. Konteynerler, "benim makinemde çalışıyordu" sorununu büyük ölçüde ortadan kaldırarak, bir servisin farklı ortamlarda tutarlı biçimde çalışmasını garanti altına aldı.
2014 yılında Google tarafından tanıtılan ve şirketin iç sistemlerinde uzun süredir kullandığı Borg altyapısından esinlenen Kubernetes, onlarca hatta yüzlerce konteynerin otomatik biçimde yönetilmesini, ölçeklendirilmesini ve hata durumunda yeniden başlatılmasını mümkün kıldı. Kubernetes'in 2015 yılında Cloud Native Computing Foundation'a (CNCF) devredilmesi, teknolojinin açık kaynaklı bir endüstri standardına dönüşmesinde önemli bir adım oldu.
Servis ağı (service mesh) teknolojilerinin ortaya çıkması da bu sürecin bir başka önemli aşamasıdır. Bu teknolojiler, servisler arası iletişimin güvenliği, izlenebilirliği ve hata toleransı gibi konuları uygulama kodundan ayrıştırarak altyapı katmanında yönetilebilir hale getirdi.
API ağ geçitleri (API gateway) de mikroservis mimarisinin yaygınlaşmasında pratik bir rol oynadı. Dışarıdan gelen tüm isteklerin tek bir giriş noktasından geçmesini sağlayan bu bileşenler, kimlik doğrulama, hız sınırlama ve isteklerin ilgili servise yönlendirilmesi gibi ortak sorumlulukların her serviste ayrı ayrı tekrarlanmasının önüne geçti.
Bugün mikroservis mimarisi, özellikle büyük ölçekli ve hızlı büyüyen teknoloji şirketlerinde yaygın olarak benimsenmektedir. Ancak bu mimarinin her proje için doğru tercih olmadığı da sektörde giderek daha net biçimde kabul görüyor. Küçük ekipler ve görece basit uygulamalar için mikroservislerin getirdiği dağıtık sistem karmaşıklığı, sağladığı faydalardan daha ağır basabilir.
Birçok kurum, bu dengeyi gözeterek "monolitik ilk" (monolith-first) yaklaşımını benimsiyor: sistem başlangıçta tek bir bütün olarak geliştirilip, gerçek ölçeklenme ihtiyaçları netleştikçe belirli bölümler bağımsız servislere ayrılıyor. Bu yaklaşım, erken aşamada gereksiz mimari karmaşıklığından kaçınmayı amaçlıyor. Nitekim bir sistemin gerçekten hangi bölümlerinin bağımsız ölçeklenmeye ihtiyaç duyduğu, çoğu zaman ancak sistem gerçek kullanıcı yüküyle bir süre çalıştıktan sonra netleşir; bu bilgiye sahip olmadan yapılan erken bölümleme kararları, ileride yeniden düzenlenmesi gereken yanlış sınırlar çizme riski taşır.
Gözlemlenme (observability) araçlarının olgunlaşması da mikroservislerin günümüzdeki uygulanabilirliğini artıran önemli bir faktördür. Dağıtık izleme (distributed tracing), merkezi günlükleme (centralized logging) ve metrik toplama araçları, onlarca servisten oluşan bir sistemde bir hatanın kaynağını bulmayı önceki dönemlere kıyasla çok daha yönetilebilir hale getirmiştir. Bu araçlar olmadan, bir kullanıcı isteğinin hangi servisler zincirinden geçtiğini ve hangi noktada yavaşladığını tespit etmek, dağıtık sistemlerde neredeyse imkânsız hale gelebilir.
Kurumların bir kısmı da "modüler monolit" adı verilen ara bir yaklaşımı tercih ediyor. Bu yaklaşımda uygulama tek bir dağıtım birimi olarak kalırken, kod tabanı içinde net sınırlarla ayrılmış modüller oluşturuluyor. Bu, ileride gerçekten gerekli görüldüğünde belirli modüllerin bağımsız servislere dönüştürülmesini kolaylaştıran, ama başlangıçta dağıtık sistem karmaşığını üstlenmeyen dengeli bir seçenek olarak değerlendiriliyor.
Önümüzdeki dönemde, sunucusuz (serverless) hesaplama modelleriyle mikroservis mimarisinin daha da iç içe geçmesi beklenmektedir. Bu yaklaşımda geliştiriciler, sunucu yönetimiyle hiç ilgilenmeden, yalnızca belirli bir işlevi tetikleyen kod parçaları yazarak sistem oluşturabilir.
Yapay zekâ destekli operasyon araçlarının (AIOps), dağıtık sistemlerdeki anormallikleri daha erken tespit edip otomatik biçimde müdahale etmesi de olası bir gelişme yönü olarak değerlendirilmektedir. Ancak bu öngörülerin ne hızda ve hangi kapsamda gerçekleşeceği, mevcut teknoloji eğilimlerine dayanan bir tahmin olmanın ötesine geçmemektedir.
Platform mühendisliği (platform engineering) yaklaşımının yaygınlaşması da bu alandaki bir diğer eğilim olarak öne çıkıyor. Bu yaklaşımda kurumlar, geliştirici ekiplerin mikroservisleri kendi başlarına dağıtıp yönetebilmesi için standartlaştırılmış iç araçlar ve altyapılar geliştiriyor. Bu, dağıtık sistemlerin karmaşıklığını geliştiricilerden bir ölçüde soyutlayarak, mikroservis mimarisinin operasyonel yükünü azaltmayı hedefliyor.
Mikroservis mimarisi, büyük ve karmaşık yazılım sistemlerinin daha yönetilebilir parçalara bölünmesini sağlayan güçlü bir yaklaşımdır. Ancak bu güç, dağıtık sistemlerin getirdiği ek karmaşıklıkla birlikte gelir; servisler arası iletişim, veri tutarlılığı ve operasyonel yönetim gibi konular dikkatli bir mühendislik yaklaşımı gerektirir.
Bu nedenle mikroservislere geçiş kararı, moda bir teknoloji trendini takip etmekten çok, kurumun gerçek ölçeklenme ihtiyaçlarına ve organizasyonel yapısına dayanan bilinçli bir mühendislik değerlendirmesi olmalıdır.
Deneyimli mühendislik ekiplerinin sıkça vurguladığı bir ilke, bir sistemin "mikroservislere hazır" olup olmadığının, servis sayısıyla değil; ekibin dağıtık sistemleri izleme, hata ayıklama ve operasyonel olarak yönetme olgunluğuyla ölçülmesi gerektiğidir. Bu olgunluk düzeyine ulaşmadan yapılan bir geçiş, teorik avantajları pratikte yaşanan operasyonel zorluklara feda edebilir.
Mikroservis mimarisi doğru koşullarda önemli bir esneklik ve ölçeklenebilirlik kazanımı sunarken, yanlış zamanlanmış bir geçiş gereksiz karmaşıklığa yol açabilir. Büyük Savunma Yazılım olarak özel yazılım geliştirme projelerimizde, mimari kararları her zaman projenin gerçek ölçek ve büyüme ihtiyaçlarına göre şekillendiriyoruz; çünkü doğru mimari, popüler olan değil, ihtiyaca uygun olandır.