Skip to main content

2 posts tagged with "microservice"

View All Tags

Monilith ve Microservice Mimarilerine Genel Bakis

Isik

Isik

Backend Team Lead @ vivoo

Modern Yazılım Mimarisi (Modern Software Architecture): Monolitik (Monolithic) ve Mikroservisler (Microservices) Arasındaki Dengeyi Kurmak (Balancing the Two)#

Giriş (Introduction)#

Yazılım mimarisi (Software architecture), projelerin uzun vadeli başarısını belirleyen kritik bir unsurdur. Doğru mimari seçimler (architectural decisions), bir yazılımın gelecekteki ölçeklenebilirliği (scalability), esnekliği (flexibility) ve sürdürülebilirliği (sustainability) üzerinde doğrudan etkiye sahiptir. Monolitik yapılardan (Monolithic structures) mikroservislere (Microservices) geçiş günümüzde yaygın bir trend haline gelse de, bu dönüşüm her zaman en iyi seçenek olmayabilir. Bu yazıda, monolitik (monolithic) ve mikroservis mimarileri (microservices architectures) arasındaki derin farkları, bu mimarilerin güçlü (strengths) ve zayıf yönlerini (weaknesses), ve bir yazılım lideri olarak bu iki yaklaşım arasındaki dengeyi nasıl kurabileceğinizi detaylı bir şekilde inceleyeceğiz.

Monolitik Mimari (Monolithic Architecture): Güçlü (Strengths) ve Zayıf Yönleri (Weaknesses)#

Monolitik mimari (Monolithic architecture), tüm uygulamanın tek bir kod tabanında (single codebase) toplandığı geleneksel bir yapı sunar. Bu yapı, geliştiricilere (developers) hızlı bir başlangıç sağlarken, projenin büyümesiyle birlikte karmaşık bir hale gelebilir.

Avantajları (Advantages):

  • Basitlik (Simplicity): Monolitik yapılar (Monolithic structures), tek bir deployable unit olarak çalıştıkları için, sistemin genel karmaşıklığı (complexity) başlangıçta düşük tutulur. Bu, küçük veya orta ölçekli projelerde (small or medium-sized projects), hızlı geliştirme (rapid development) ve dağıtım süreçlerine (deployment processes) imkan tanır.
  • Düşük Overhead: Birden fazla servis (multiple services) ve bunların iletişim protokolleri (communication protocols) yerine, tek bir süreç (single process) içerisinde çalışmak, sistemin iletişim ve koordinasyon maliyetlerini (communication and coordination costs) düşürür. Bu da performans avantajı (performance advantage) sağlar.
  • Kolay Hata İzleme (Easy Error Tracking): Sistem tek bir birim (single unit) olarak çalıştığı için, hata ayıklama (debugging) ve izleme süreçleri (monitoring processes) daha az karmaşık hale gelir. Loglar (logs) ve hata raporları (error reports) merkezi bir noktada toplanır.

Dezavantajları (Disadvantages):

  • Teknoloji Borcu (Technical Debt): Proje büyüdükçe, kod tabanındaki bağımlılıklar (dependencies) ve modüller arasındaki sıkı bağlılıklar (tight couplings), teknik borcun (technical debt) birikmesine neden olur. Bu, yeni özelliklerin (new features) eklenmesini ve mevcut hataların (existing bugs) düzeltilmesini zorlaştırır.
  • Dağıtım Riski (Deployment Risk): Tek bir modüldeki (module) hata veya değişiklik, tüm sistemin çökmesine neden olabilir. Bu nedenle, her deploy işlemi (deployment process) daha fazla risk ve dikkat gerektirir.
  • Ölçeklenebilirlik Kısıtları (Scalability Constraints): Monolitik sistemlerde (Monolithic systems), yalnızca belirli bir modülün (module) ölçeklenmesi mümkün değildir; tüm sistemin ölçeklendirilmesi gereklidir. Bu, kaynak israfına (resource waste) yol açabilir ve maliyetleri (costs) artırabilir.

Teknik Derinlik (Technical Depth):

Monolitik mimarilerde (Monolithic architectures), örneğin, veritabanı katmanının (database layer) tüm uygulama tarafından merkezi olarak paylaşılması (centralized sharing), performans sorunlarına (performance issues) neden olabilir. Bir modülün veritabanına aşırı yük bindirmesi (overloading the database) durumunda, bu yük tüm sistemi etkiler. Bu tipik bir "N+1 sorgusu" problemi (N+1 query problem) olarak kendini gösterebilir ve çözümü, veritabanı katmanında yapılan optimizasyonlar (optimizations in the database layer) ve cache stratejileri (caching strategies) ile sağlanabilir.

Mikroservis Mimarisi (Microservices Architecture): Esneklik (Flexibility) ve Ölçeklenebilirlik (Scalability)#

Mikroservis mimarisi (Microservices architecture), her biri bağımsız olarak çalışabilen (independently functioning), belirli işlevlere sahip küçük hizmetlerden (small services) oluşur. Bu yapı, özellikle büyük ve karmaşık sistemlerde (large and complex systems) ölçeklenebilirlik (scalability) ve esneklik (flexibility) sağlar.

Avantajları (Advantages):

  • Hizmet Bağımsızlığı (Service Independence): Mikroservisler (Microservices), kendi veri depolarına (datastores) sahip olup, bağımsız olarak geliştirilebilir (developed) ve dağıtılabilir (deployed). Bu, bir hizmetin (service) güncellenmesi (updating) veya hatasının düzeltilmesi (fixing a bug) gerektiğinde, tüm sistemin yeniden dağıtılmasına (redeploying) gerek kalmadan hızlı bir şekilde müdahale edilebilmesini sağlar.
  • Teknoloji Esnekliği (Technology Flexibility): Her bir mikroservis (microservice), ihtiyaçlarına en uygun teknoloji yığınını (technology stack) kullanabilir. Örneğin, yüksek işlem gücü gerektiren bir mikroservis (compute-intensive microservice) Go ile yazılabilirken, başka bir servis (another service) Python gibi daha uygun bir dilde geliştirilebilir.
  • Yüksek Ölçeklenebilirlik (High Scalability): Her mikroservis (microservice), bağımsız olarak yatay veya dikey olarak ölçeklenebilir (scalable horizontally or vertically). Bu, özellikle yüksek trafikli modüller (high-traffic modules) için büyük bir avantaj sağlar.

Dezavantajları (Disadvantages):

  • Dağıtık Sistem Karmaşıklığı (Distributed System Complexity): Mikroservisler arasındaki iletişim (communication between microservices), senkronizasyon (synchronization) ve veri tutarlılığı (data consistency), sistemin karmaşıklığını (complexity) artırır. Özellikle dağıtık bir sistemde (distributed system), hizmetler arası gecikmeler (latency between services), ağ sorunları (network issues) veya partial failure durumları ciddi sorunlara yol açabilir.
  • Veri Yönetimi ve Tutarlılık (Data Management and Consistency): Her mikroservisin (microservice) kendi veritabanına (database) sahip olması, verilerin tutarlılığını (data consistency) ve senkronizasyonunu (synchronization) zorlaştırır. Özellikle ACID (Atomicity, Consistency, Isolation, Durability) prensiplerinin (principles) tam olarak uygulanması güçleşir ve eventual consistency modelleri (eventual consistency models) devreye girer.
  • Operasyonel Yük (Operational Overhead): Mikroservislerin yönetimi (managing microservices), loglama (logging), monitoring (izleme), güvenlik (security) ve hata yönetimi (error handling) gibi operasyonel alanlarda (operational areas) ekstra yük getirir. Bu, ekibin DevOps yetkinliklerini (DevOps skills) ve otomasyon araçlarını (automation tools) ileri düzeyde kullanmasını gerektirir.

Teknik Derinlik (Technical Depth):

Mikroservis mimarilerinde (Microservices architectures), servisler arasındaki iletişim (communication between services) genellikle REST, gRPC, veya message broker (Kafka, RabbitMQ) gibi teknolojiler üzerinden sağlanır. Ancak bu, servisler arasında artan bir latency'ye neden olabilir ve veri tutarlılığını (data consistency) sağlamak için distributed transactions veya saga patterns gibi ileri seviye çözümlerin kullanılmasını gerektirir.

Hangi Durumda Hangi Mimari? (Which Architecture to Choose?)#

Monolitik (Monolithic) veya mikroservis mimarisi (microservices architecture) seçimi, projenin gereksinimlerine (requirements), ekibin yapısına (team structure) ve gelecekteki ölçeklenebilirlik hedeflerine (scalability goals) göre belirlenmelidir.

  • Proje Büyüklüğü (Project Size): Eğer proje küçükse (small project) ve karmaşıklık düşükse (low complexity), monolitik bir mimari (monolithic architecture) daha hızlı ve maliyet etkin (cost-effective) olabilir. Ancak, projenin büyüme potansiyeli (growth potential) varsa, mikroservislere geçiş (transition to microservices) daha sürdürülebilir (sustainable) bir çözüm olabilir.
  • Geliştirme Ekibi Yapısı (Development Team Structure): Küçük ve orta ölçekli ekiplerde, monolitik yapı, ekip üyeleri arasındaki koordinasyonu kolaylaştırır. Büyük ekiplerde ise, mikroservis yapısı ile farklı ekipler birbirinden bağımsız olarak çalışabilir ve paralel geliştirme süreçleri hızlanır. Tabii, işte istediğiniz şekilde her Türkçe terimin yanında İngilizce karşılıkları ile içeriğiniz:
  • Ölçeklenebilirlik Gereksinimi (Scalability Requirement): Eğer uygulamanızın çok geniş bir kullanıcı tabanına hizmet vereceği öngörülüyorsa, mikroservis mimarisi (microservices architecture), esnek ve ölçeklenebilir bir çözüm sunar. Özellikle belirli modüllerin bağımsız olarak ölçeklendirilmesi gerektiğinde, mikroservisler (microservices) tercih edilmelidir.
  • Teknoloji Çeşitliliği (Technology Diversity): Eğer proje farklı teknolojik çözümler gerektiriyorsa, mikroservisler (microservices) bu çeşitliliği yönetmeyi kolaylaştırır. Her bir mikroservisin (microservice) kendine özgü bir teknoloji yığını (technology stack) olabilir.

Pratik Bir Örnek#

Önceki deneyimlerimde, başlangıçta monolitik bir yapı (monolithic structure) ile hızlı bir şekilde piyasaya giriş yaptık. Ancak, kullanıcı tabanımız genişledikçe ve sistem karmaşıklığı (system complexity) arttıkça, mikroservis mimarisine (microservices architecture) geçiş yapmak kaçınılmaz hale geldi. Bu geçiş sürecinin farklı ekiplerçe farklı zorlukları vardı ve dikkatli bir planlama gerektiriyordu.

  1. Mevcut Sistem Analizi (Current System Analysis): İlk adım olarak, mevcut monolitik yapının (monolithic structure) detaylı bir analizi (detailed analysis) yapıldı. Servisler arasındaki bağımlılıklar (dependencies), performans darboğazları (performance bottlenecks) ve sistemin genel mimarisi (overall architecture) detaylı bir şekilde incelendi.
  2. Domain-Driven Design (DDD) Yaklaşımı (Domain-Driven Design Approach): Sistemdeki farklı işlevleri bağımsız domain'ler (domains) olarak ayrıştırdık. Bu yaklaşım, her bir domain'in kendi sorumluluk alanına sahip olmasını sağladı ve bu domain'ler, bağımsız mikroservisler (independent microservices) olarak tanımlandı.
  3. Veri Yönetimi Stratejisi (Data Management Strategy): Mikroservisler arası veri tutarlılığı (data consistency), CQRS (Command Query Responsibility Segregation) ve Event Sourcing yaklaşımları (Event Sourcing approaches) kullanılarak sağlandı. Bu sayede, veri tutarlılığı sağlanırken, sistemin esnekliği (flexibility) ve performansı (performance) artırıldı.
  4. Yeni Mimarinin Uygulanması (Implementation of the New Architecture): Mikroservis sınırları (microservice boundaries) ve sorumluluk alanları (responsibility areas) belirlenip, her bir servisin bağımsız olarak geliştirilmesi (independent development) ve test edilmesi sağlandı. Ayrıca, Kubernetes tabanlı bir altyapı (Kubernetes-based infrastructure) ile servislerin dağıtımı (deployment) ve ölçeklenmesi (scaling) otomatik hale getirildi.
  5. Aşamalı Geçiş (Gradual Transition): Monolitik yapıdan (monolithic structure) mikroservislere (microservices) geçiş, her seferinde bir mikroservis olacak şekilde aşamalı olarak yapıldı. Bu geçişleri yaparken Strangler Modeli yani her bir servisi yayınlarken diğerini boğma stratejisini izledik. Bu, sistemin stabilitesini (system stability) koruyarak, kullanıcı deneyiminin (user experience) bozulmasını önledi.

Sonuç (Conclusion)#

Monolitik ve mikroservis mimarileri (monolithic and microservices architectures) arasındaki dengeyi kurmak, startup, yazılım dünyasında stratejik bir karar süreç olduğunu gördük. Her iki mimarinin de kendine özgü güçlü ve zayıf yönleri (strengths and weaknesses) var olduğunu, doğru mimari seçiminin (architectural choice), projenizin gereksinimlerine (requirements), ekibinizin yeteneklerine (team capabilities) ve uzun vadeli hedeflerinize (long-term goals) göre belirlenmesi ve maliyetinden dolayı sürekli değişiklik yapılmasının küçük bir ekip / startup için iyi olmadığını gözlemledik. Özellikle backend takımında, bu kararları verirken teknik derinliği (technical depth) ve operasyonel gereksinimleri (operational requirements) dikkatle değerlendirmenin, kısa ve uzun vadede projenin başarısı için gerekli olduğunu farkettik.

Nx ve NestJs Kullanarak Monorepo Yönetimi

Isik

Isik

Backend Team Lead @ vivoo

Nx monorepo yönetim aracı, Nest.js Nodejs frameworkü Nx, Node.js tabanlı front end ve backend uygulamalarını ve pluginleriyle beraber diğer ortamları da destekleyebilen(örneğin nx-go ile golang) bir monorepo yönetim aracıdır.

Öncelikle monorepodan bahsedelim, monorepo nedir? Monorepo isminden de anlaşıldığı gibi projenin tüm kaynak kodları ve assetlerinin tek bir repoda toplandığı bir proje repository yönetim stratejisidir. En bilindik şirketlerden olan Google, bu yaklaşımı benimseyerek milyarlarca satır kodu tek bir repoda tutuyor. Detaylar için: https://dl.acm.org/doi/fullHtml/10.1145/2854146

Monolith bir uygulama tek bir repoda da yönetilebilir, birden fazla repoya da bölünebilir. Benzer şekilde microservislerden oluşan çoklu uygulamalar tek repoda yani bu yazımızda geçen monorepo olarak da yönetilebilir kendi multi repolarına da bölümlenebilir.

Biz vivoo’da farklı bölümler/projeler için farklı stratejiler izlesek de birazdan bahsedeceğim backend/api için monorepo yönetim aracı olan nx’i kullanarak microservisleri yönetiyoruz.

Monorepo kullanarak sağladığımız belirgin avantajlar şu şekilde: 1- Backend servisleri — projeleri arasındaki kullanılan ortak kütüphaneler, bağımlılıklar ve veri tiplerini kolayca birbirine paylaştırabiliyoruz.

2- Tek bir altyapı yapılandırmasını tüm servislerde kullanabiliyoruz. Aslında bu konu monorepoların derleme sürelerinde bir dezavantaj gibi gözükse de nx, docker ve bir kaç bash script sayesinde bu dezavantaj oldukça azalıyor. Sadece affected projeleri paketleyip yayınlayabiliyoruz.

3- Kullanılan dependencyler tüm projelerde en güncel haliyle kalıyor. Bir tanesini güncellediğimizde hepsi güncelleniyor.

Monorepoya geçerken dikkat edilmesi gereken önemli bir şey testlerin yazılıyor olması. Örneğin x librarysini kullanan 20 tane uygulama/proje/servis varsa ve bir kişi gidip x librarysini güncellediğinde o kütüphane ya da uygulamaların bazılarında testler doğru yazılmadıysa ortak kullanılan yerdeki bir değişiklik bir çok serviste yayın sırasında hataya sebep olabilir. O yüzden dikkatli olunması gerekiyor.

Neden NX tercih ettik? An itibariyle(2021) açık kaynak kodlu javascript teknolojileri üzerinde yönetim aracı olarak kullanabilecek 2 opsiyon var. Bu opsiyonlar altında yarn kullanıldığı için yarn workspace’i dahil etmedim.

1- Nx

2- Lerna

Lerna oldukça güçlü bir araç olsa da bizim her servisi ayrıca paket olarak tasarlamaya ve bunları yayınlamaya değil de şirket içerisinde uygulama geliştirmeyi daha efektif hale getirmeye ihtiyacımız vardı. Google’ın yaklaşımlarını benimseyerek geliştirilen Nx, Lerna’dan farklı olarak tüm proje için tek bir package.json sağlıyor. Lerna’da her projenin kendi package.json’ı varken nx’de tüm dependencyler tek bir yerden yönetiliyor. Nx’de istenirse yarn workspace kullanarak bunu çoklu paket sistemine çevirmek mümkün olsa da pratik olarak önerilmiyor. Google’ın yaklaşımı burada baz alınmış. https://github.com/nrwl/nx/issues/1777 bu issue’dan ilgili yazışmaları okuyabilirsiniz. Lerna’dan Nx’ geçmek ile alakalı kurucularından bir Victor Savkin’in yazdığı bir blog var detayları buradan okuyabilirsiniz. https://blog.nrwl.io/why-you-should-switch-from-lerna-to-nx-463bcaf6821

Nx’in öne çıkan özelliklerine biraz daha bakalım:

Dependency Graph: Nx ile proje grafiğindeki düğümler workspace.json içinde tanımlanıyor. Düğümler arasındaki bağımlılıkları nx kendi başına halletse de manuel olarak tanımlayabiliyoruz. Ayrıca önceden tanımlanmış grafiği önbelleğe alıp yalnızca değiştirdiğiniz dosyaları yeniden analiz ediyor. Affected Commands: Nx ile ilgili projeyi, çoklu projeleri ve tüm projeleri çalıştırmak dışında yalnızca değişen ve bu değişikliklere bağlı olan uygulamaları test etme, build etme imkanımız var. İlgili merge/pull request ile target ve base commit arasındaki farka bakıp sadece etkilenen projeleri çalıştırabiliyoruz. Computation Hashing and Caching: Nx ilgili bir görevi çalıştırken task sırasını kontrol eder ve çalıştırdığı her görevi hashler. Eğer ilgili görev daha önce çalıştırıldıysa yani hash cache’de mevcutsa ilgili işin çıktılarını hızlı bir şekilde yerine getirir. Bunu yaparken local ya da cloud kullanılabilir. Distributed Task Execution: Nx’in komutlarını NX Cloud kullanarak farklı makinelerde aynı anda çalıştırabilirsiniz. Nx Generators: Nx generatorler ile projemizde kullanılan ve tekrar eden bir çok görevi ve yapıları özelleştirerek otomatikleştirebiliyoruz. Örneğin sürekli kullandığımız bir dosya yapısı varsa onu oluşturmasını sağlayan bir generator yazabiliriz. Öyleyse hadi Nx’i sistemimize kurup nest js ile bir uygulama ayağa kaldıralım. İkinci bölümde ise bu uygulamaya yeni bir uygulama ekleyip build, test ve deploy süreçlerine bakalım.

​​npm install -g nx

npx create-nx-workspace — preset=nest

Nx cli

nx workspace

Vs code ile ilgili projeyi açıp structere’a bakalım:

project_structure_architecture

Nx-NestJs CLI ile oluşturulmuş uygulama dosya dizini Nx ve Nest CLI sayesinde https://nx.dev/l/n/nest/overview buradaki dökümanda bulabileceğiniz yardımcı commandler işleri oldukça hızlandırıyor.(not: yarn add -D @nrwl/nest ile nesti eklemeyi unutmayalım)

Bir sonraki bölümde Nx kullanarak birden çok uygulamanın test, build ve deploy süreçlerini anlatıp bir kaç örnek yapacağız.

Yazıda ve projede emeği geçen kişiler:

Huseyin Isik

Semih Onay