13 Ocak 2023 Cuma

SOLID prensiplerinden Open-Closed prensibinin python kodları eşliğinden yakından incelenmesi

Merhaba, 

Bugünkü yazımızda SOLID prensiplerinden 'Open-Closed Rrensibi' ni inceleyeceğiz.


Temsil edilen O harfi "Open-Closed Principle" (Açık-Kapalı Prensibi) anlamına gelir. Bu prensip, bir sınıfın veya modülün geliştirilmeye açık olmasının yanı sıra, uzantılar yapılarak kapatılması gerektiğini ifade eder. Bu, sınıfın veya modülün işlevselliğini genişletmek için değiştirilmemesi, ancak ekstra işlevsellik eklemek için uzantılar yapılması gerektiğini ifade eder.

Open-Closed Principle kullanmak, yazılımın daha esnek, daha test edilebilir ve daha az hata içerme olasılığına sahip olmasını sağlar. Özellikle, bu prensip ile yazılım kodunun değiştirilmesi gerektiğinde, mevcut kodun değiştirilmemesi ve yerine yeni kodun eklenmesiyle sorunlar çözülebilir. Bu, yazılımın daha uzun bir ömür yaşamasına ve daha az bakım gerektirmesine olanak tanır. Ayrıca, Open-Closed Principle kullanmak, yazılımın daha kolay test edilebilir olmasını sağlar çünkü sınıflar ve modüller arasındaki bağımlılıklar azaltılmış olur.

Open-Closed Principle kullanmazsanız, yazılım kodunuzun daha az esnek ve daha zor test edilebilir olmasına neden olabilir. Kodunuzun işlevselliğini genişletmek istediğinizde, mevcut kodun değiştirilmesi gerekebilir ve bu değişiklikler, başka yerlerde beklenmedik hatalara neden olabilir. Ayrıca, mevcut kodun değiştirilmesi, yazılımın daha hızlı çürümesine ve daha fazla bakım gerektirmesine neden olabilir.

Bunu bir python kodu ile açıklayalım :

 

class Rectangle: def __init__(self, width, height): self._width = width self._height = height def get_area(self): return self._width * self._height class Square(Rectangle): def __init__(self, side): super().__init__(side, side) class Circle: def __init__(self, radius): self._radius = radius def get_area(self): return 3.14 * (self._radius ** 2) class AreaCalculator: def __init__(self, shapes): self._shapes = shapes def get_total_area(self): total_area = 0 for shape in self._shapes: total_area += shape.get_area() return total_area


Burada, Rectangle, Square ve Circle sınıfları, get_area metodunu kullanarak alanlarını hesaplamak için tasarlanmıştır. AreaCalculator sınıfı ise, verilen şekillerin alanlarını toplamak için kullanılır. Bu örnekte, Rectangle ve Circle sınıfları "Open-Closed Principle" prensibine uymaktadır. Çünkü, bu sınıfların işlevselliği genişletilmek istendiğinde değiştirilmemektedir, ancak yeni sınıflar oluşturulmaktadır (örneğin Square sınıfı) ve bu sınıflar, genişletilmiş işlevselliği sağlamaktadır.

Bu örnekte, AreaCalculator sınıfı, verilen şekillerin alanlarını toplamak için kullanılmaktadır. Bu, AreaCalculator sınıfının, verilen şekillerin türlerinden bağımsız olarak çalışmasını sağlar. Bu, AreaCalculator sınıfının daha esnek ve daha az hata içerme olasılığına sahip olmasını sağlar.

Şimdi de bu prensibin ihlal edildiği bir örnek oluşturalım.

 

class Rectangle: def __init__(self, width, height): self._width = width self._height = height def get_area(self): return self._width * self._height class Square: def __init__(self, side): self._side = side def get_area(self): return self._side * self._side class Circle: def __init__(self, radius): self._radius = radius def get_area(self): return 3.14 * (self._radius ** 2) class AreaCalculator: def __init__(self, shapes): self._shapes = shapes def get_total_area(self): total_area = 0 for shape in self._shapes: if isinstance(shape, Rectangle) or isinstance(shape, Square): total_area += shape.get_area() return total_area


Burada, Rectangle, Square ve Circle sınıfları, get_area metodunu kullanarak alanlarını hesaplamak için tasarlanmıştır. AreaCalculator sınıfı ise, verilen şekillerin alanlarını toplamak için kullanılır. Bu örnekte, Rectangle ve Square sınıfları "Open-Closed Principle" prensibini ihlal etmektedir. Çünkü, AreaCalculator sınıfı, Rectangle ve Square sınıflarının alanlarını toplamak için kodunu değiştirmiştir. Bu değişiklikler, AreaCalculator sınıfının başka yerlerde beklenmedik hatalara neden olmasına olanak tanır.

Ayrıca, AreaCalculator sınıfı, Circle sınıfını dahil etmemektedir. Bu, AreaCalculator sınıfının daha az esnek ve daha zor test edilebilir olmasına neden olur.

Biraz daha detaya inersek :

 
def get_total_area(self): total_area = 0 for shape in self._shapes: if isinstance(shape, Rectangle) or isinstance(shape, Square): total_area += shape.get_area() return total_area

Bu kod bloğunda, AreaCalculator sınıfı, verilen şekillerin her birisini döngü içinde kontrol eder. Eğer şekil bir Rectangle sınıfı veya Square sınıfının bir örneği ise, şeklin alanını toplamaya ekler. Bu, AreaCalculator sınıfının sadece Rectangle ve Square sınıflarının alanlarını toplamasını sağlar.

Bu örnekte, AreaCalculator sınıfı, Rectangle ve Square sınıflarının alanlarını toplamak için kodunu değiştirmiştir. Bu değişiklikler, AreaCalculator sınıfının, daha sonra eklenen yeni şekil türlerini dahil etmemesi sonucunu doğurur. Örneğin, Circle sınıfının alanını toplamak istediğinizde, AreaCalculator sınıfının kodunu değiştirmek zorunda kalırsınız. Bu, AreaCalculator sınıfının daha az esnek ve daha zor test edilebilir olmasına neden olur.

Bu nedenle, Open-Closed Principle kullanmak, yazılımın daha esnek, daha test edilebilir ve daha az hata içerme olasılığına sahip olmasını sağlar.





Aşağıda bir başka kod parçacığında ihlalin nasıl olduğunu tekrar görelim.
 
class Shape: def __init__(self, type): self.type = type def draw(self): if self.type == "circle": # draw circle elif self.type == "square": # draw square # ...

Bu kod parçası, "open-close" prensibini ihlal eder çünkü "Shape" sınıfı, eklenen yeni bir şekil için her seferinde değiştirilmelidir. Örneğin, eğer yeni bir şekil tipi olarak "dikdörtgen" eklenirse, "Shape" sınıfının "draw" metodu değiştirilmelidir. Bu, "Shape" sınıfının açık olduğu anlamına gelir, ancak her yeni şekil için değiştirilmesi gerektiğinden kapalı değildir. Bu nedenle, "Shape" sınıfının değiştirilmesine gerek kalmadan yeni şekil tipleri eklenmelidir.

Teşekkürler
Cem Selmanoğulları

8 Şubat 2022 Salı

Mikroservis Mimarisi ve Python Özelinde Uygulamalar

Merhaba,

Bugünkü yazımda Mikroservis mimarisine giriş yapıp, python yazılım dili özelinde bazı incelemeler yapmaya çalışacağım.

Mikroservis mimarisine geçmeden önce Monolitik uygulama mimarisine bakmakta fayda görüyorum. 



Üstteki resim Monolitik ve Mikroservis mimari yapıları arasındaki farkı anlamamız için oldukça güzel bir örnek olacaktır. Monolitik mimaride veritabanından sunumun yapıldığı kullanıcı arayüzüne kadar tüm akış genellikle tek dilde yazılan tek bir uygulama şeklindedir. Kullanıcı sunum katmanı, iş akışının oluşturulduğu katman ve veri erişim katmanları aynı codebase içindedir. Küçük projelerde bu yapı oldukça kolay anlaşılabilir ve yönetilebilir olabilir. 

Fakat uygulama büyürse ve codebase e yeni özellikler eklemek gerekirse ne olacak? 

Projenin yönetimi ile ilgili çeşitli zorluklar baş gösterecektir. Ayrıca projede hızlı bir büyüme ve çeşitlenme ölçeklenebilirlikle ilgili sıkıntıları ortaya çıkaracaktır. Codebase çok büyürse farklı codebaselere bölünerek farklı takımlar halinde ilerlemek ve kurulum aşamasında codebaselerin birleştirilmesi gibi bir çözüm düşünülebilir fakat buda ekstra efor ve iş yükü anlamına gelecektir. 

  • Monolitik yapıdaki en büyük sıkıntılardan biri ise uygulama çok büyüdüğünde bir hata keşfedilirse, hatanın düzeltilmesi, etki analizi ve testlerinin yapılması sıkıntılara neden olacaktır. Küçük bir bug tespitinde bile kapsamlı testler yapılması ve tüm uygulamanın tekrar dağıtılması gerekecektir. 
  • Her güncelleme uygulamanın bütünüyle tekrar deploy edilmesine sebep olur. Küçük değişikliklerde bile kapsamlı regresyon testi gerekir. 
  • Monolitik yapılarda genellikle tek bir dil kullanıldığı için çözüm ve modülerite gereken noktalarda o dile bağımlı durumda kalırsınız. 
  • Bu yapıda çok büyüyen projelerde yeni dahil olan mühendislerin uygulamaya hakim olma süreci de uzayacaktır. 
  • Monolitik mimaride geliştirilmiş bir uygulamanın herhangi bir bileşeni kullanıcılar tarafından çok yük ile karşı karşıya bırakılırsa performansı arttırmak için yazılımın sadece bu bileşenini ayırıp ikinci sunucuya koymamız mümkün değildir. Bu anlamda bir performans artışı için tüm yazılımı ikinci sunucuya kurmak gerekecektir. Fakat ikinci sunucuya diğer tüm bileşenleri de tekrar kurmuş olacaksınız. Bu da kaynakların verimsiz tüketilmesine de sebep olacaktır.
  • Herhangi bir modüldeki hata tüm sistemi etkileyecektir.
  • Yeni teknolojileri alıp kullanma konusunda birtakım bariyerlere sahiptir. Çünkü kullanılan dil ve frameworkteki değişiklikler tüm uygulamayı etkilemektedir. Yeni bir teknolojiyi bir bileşende entegre etmeye çalışmak, işi tüm uygulamayı yeniden yazmaya kadar götürebilir.



Mikroservis mimarisinde ise "Böl ve Yönet" sloganını aklımıza getirebiliriz. Küçük ve birbirinden bağımsız çalışabilen ama gerektiğinde birbiri ile konuşabilen uygulamalardan oluşan bu mimari, genişlemeye ve yatay büyümeye oldukça açıktır. Büyümeye açık olması çok iyi bir dizaynı da gerekli kılar. Yapılacak dizayn anlaşılabilir, yönetilebilir ve uzun vadede bu yönetilebilirliğini koruyabilmelidir. 

  • Bu mimaride problemlerin kök kaynağı ve etki ettikleri noktalar hızlıca tespit edilebilir ve daha hızlı müdahale edilerek yeni geliştirme gereken durumlarda daha hızlı deploy sağlanabilir.
  • Sistemin daha yönetilebilir alt parçalar olan servislere ayırmak karmaşıklık probleminin üstesinden gelmemizi sağlar.
  • Farklı mikroservisler için farklı yazılım dilleri rahatlıkla kullanılabilir. Servisi kullanacak taraflar için servisin geliştirildiği yazılım dili önemli değildir. Karşı taraf sadece servisin arayüzünü kullanacaktır.  Buda uygulama geliştirmeye büyük bir esneklik ve modülerlik getirir. 
  • Sisteme yeni bir özellik eklemek çok kolaydır. Yeni eklenen özellik için sistemin tamamının test edilmesine gerek duyulmaz, sadece etki edilen ve birbiri ile konuşan ve bağımlılığı olan mikroservislerin test edilmesi yeterlidir. 
  • Her bir mikroservisin tek bir işe odaklanmış olması sistem yapısını anlamamızı ve sorun tespitini kolaylaştırır. Ayrıca bu sayede daha performanslı çalışan bir sistem geliştirilmesine olanak sağlar. 
  • Tek ve büyük bir codebase içine gereken deploy süresi ile kıyaslarsak mikroservislerin bağımsız deploy ediliyor olması büyük avantajdır. Yani yeni bir özellik ekliyorsanız sadece yeni mikroservisi ve gerekiyorsa sadece yeni mikroservis ile konuşacak mikroservisi deploy etmeniz yeterli olacaktır. 
  • Ekibi tek bir codebase i öğrenecek şekilde yönlendirmek yerine parçalara bölüp her servis ve altındaki mikroservisleri farklı geliştiricilerin yönetmesi sağlanabilir. Buda herkesin yaptığı işte hakimiyetini arttıracaktır. Ayrıca yeni katılan kişi tüm yapı yerine kendisi ile ilgili servisi detaylı öğrenmek zorunda olacağı için adaptasyonu çok hızlı olacaktır. Mühendisler sistemin geneli hakkında bilgi sahibi olmakla birlikte kendilerine atanan mikroservisler konusunda uzman olacaktır.
  • Her servis bir takım tarafından diğer takımların seçim ve karar makanizmalarından ve kullanılan dillerden bağımsız olarak geliştirilir. Bir takım X teknolojisini kullanıyorsa diğerleri bunu kullanmak zorunda değildir. Önemli olan son durumda servislerin birbirleri ile, müşteri ike ve veri kaynakları ile konuşabiliyor olmasıdır.
  • Önemli noktalardan birisi ise bir mikroserviste bir sorun olduğunda sadece bu mikroservis ve hizmet ettiği mikroservisler etkilenir. Yazılım kalan mikroservisleri çalışmaya devam eder. Örnek vermek gerekirse bir web sitesinde arama işlevini yöneten 3 mikroservisten biri çökebilir ve bu 3 mikroservis etkilenerek arama servisi durabilir. Fakat diğer mikroservisler çalıştığı için web sitesindeki login, alışveriş, analitik, upload, yorumlama... gibi diğer servisleri hizmet vermeye devam ederler. Yani web sitesi %10 kullanılmaz hale gelirken %90 işlevsel kalacaktır.
  • Cassandra gibi sistemler ile her mikroservisi ayrı veritabanı yapısı ile çalıştırmanız da mümkündür. Buda veritabanlarında çıkacak sorun yada yapılacak güncellemeler sırasında diğer servislerin etkilenmesini önleyecektir.

Düzgün şekilde dizayn edilen mikroservis mimarisi yatayda ölçeklendirmeye imkan verecektir. Yatayda ölçeklemeden kastımız daha çok makine üzerinde loadbalancing yaparak mikroservisin birden fazla örneğinin çalıştırılmasıdır. Yani dikey ölçekleme yapıp tek bir sunucuda çalıştırıp ölçekleme gerektiğinde makineyi upgrade etmek yerine daha maliyetsiz küçük sistemlerde uygulamayı çalıştırarak yatayda genişlemiş oluyoruz. 

  • Küçük bir örnek vermek gerekirse. Web sitesinde arama yapan bir bileşen kullanıcılardan çok talep alıyor ve sunucuyu yormaya başladı ise bunu çözmenin iki yolu olacaktır. Ya sunucuyu dikey ölçekleme ile ram cpu yönünden upgrade edeceksiniz yada bir loadbalancer arkasına bu arama bileşenininden mikroservis olarak 5 tane koyarak yatay ölçekleme yapacaksınız. Sanallaştırma ve container teknolojileri ile birlikte yatay ölçekleme maliyetler açısında daha uygun olabilmekle birlikte birtakım risklerin bertaraf edilmesine de fayda sağlayacaktır. Kurulan 5 mikroservis aynı işi yaptığı için bir tanesi çökse bile loadbalancer üzerinde diğer 4 tanesi hizmet vermeye devam edecektir. Yani dikey ölçekleme yaparak, düşerse sistemi durduracak 1 servis ile hizmet vermek yerine, yatayda genişleyerek 5 tane aynı işi yapan servisi koşturarak servisi yedeklemek sureti ile hizmetin durma riskini minimize etmiş olacaksınız. Biryandan da performans sorununuzu çözmüş olacaksınız.

Mikroservisler için spesifik olarak şu sunucuda şu ortamda çalışsın gibi bir zorunluluk bulunmamaktadır. Kubernetes ve Docker teknolojileri ile birlikte bunu kolaylıkla sağlamak mümkündür. Mikroservisler stateless yapıda geliştirilirler. Çalıştıkları diskte veri saklamazlar. Böylece restart edilselerde, başka bir makinede çalıştırılsalarda işlerini yapmaya devam ederler. Verileri dışarıda sakladıkları için statefull servislere oranla daha kolay ölçeklenebilirler.  

Son yıllarda gelişen teknolojilere ve taleplere bakıldığında tabiki mikroservis mimarisi daha tercih edilebilir durmaktadır. Ama bu her durumda mikroservis mimarisini tercih etmemiz gerektiği anlamına gelmemektedir. Monolit mimarinin de avantajlı olduğu ve tercih edilmesi gereken projeler de olacaktır. Uygulamanız küçük ve çok fazla iş mantığı içermiyor ise ileriye dönük çok fazla ölçeklenebilirlik gerektirmiyor ise monolitik yapı kullanmak avantajlı olabilir. Karmaşık yapıda mikroservis mimari kuracak bilgi birikimi ve tecrübeniz yoksa aynı zamanda ekibiniz küçük ise yine monolitik mimaride başlamak avantajlı olabilir. 

Sıfırdan bir projeyi microservis mimari işe geliştirmek nispeten daha kolay olacaktır. Fakat monolit ağırlıklı yapıda halihazırda çalışan bir yapıyı mikroservis yapısına geçirmek her açıdan maliyetli ve sancılı olabilir. Evet son durumda harika bir yapıya sahip olacaksınız, peki bedel ödemeye hazır mısınız? 

Bugün tam performans ile kesintisiz çalışan Netflix firması tamda bahsettiğimize benzer bir bedel ödemişe benziyor. 2008 yılında 3 gün civarında hizmet veremeyen Netflix firması sorunun veritabanı sıkıntısı sebebi ile oluştuğunu keşfetmiştir. 

  • Netflix'in devasa sistemi tek bir veritabanına bağımlıydı. Takdir edersiniz ki bu veritabanı ile ilgili herhangi bir sıkıntı tüm sistemi etkileme potansiyeline sahiptir. Normal şartlarda firmalar düzgün çalışan sistemlerine pek dokunmak istemezler. Heleki anlık kesintilerin bile olay olabileceği Netflix gibi bir firma. Genelde de bu bakış açısı ciddi bir çöküntüye kadar devam eder ve bu çöküntü firmayı yeni bir arayışa ve önlem almaya iter. İşte 4 günlük kesinti Netflix için tamda böyle olmuştu. Netflix'in mikroservis mimariye geçiş kararını 2008 yılında aldı. Peki microservis mimariye tam geçiş nekadar sürede tamamlandı ? 2016 yılında. Yani tam 8 sene. Gerçekten bir bedel ödenmişe benziyor. Fakat sonuçta tüm dünyaya hizmet verebilecek yatay genişlemeye sahip mikroservis mimarisi üzerine kurulmuş bir yapıya kavuşuyorlar. Günümüzdeki pekçok büyük firmanın projelerine monolitik bir mimaride başladığını fakat ölçekleme ve yönetimsel problemlerden dolayı günümüzde mikroservis mimari ile devam ettiğini söyleyebiliriz.

Mikroservis mimariye geçiş birçok değişimi de beraberinde getirebiliyor. Mikroservis mimaride alışık olduğunuz legacy DB'i yeni mimariye uyumlu hale getirmek için birçok sorguda değişiklik yapılması gerekebilir. Tabloları ve şemaları mikroservislere göre yapılandırmanız gerekecektir. Yapılacak bu ayrımlar ve ek join işlemleri sistemi yönetilmesi zor hale sokmakla birlikte performans sorunlarını da beraberinde getirebilir. Bu sebeple legacy veritabanınızdan vazgeçmeniz de gerekebilir. Mikroservis mimariye daha uyumlu bir veritabanı yapısı kullanmak daha mantıklı olabilir. 


Tıpkı Apache Cassandra gibi. Cassandra açık kaynak kodlu olmakla birlikte NoSQL ve dağıtık mimaride kullanılabilecek bir yapıya sahiptir. Tamda mikroservis mimarinin aradığı şey değil mi? Yatay ölçeklemeye çok uygun olan Cassandra, Netflix tarafından da yeni mikroservis yapısı içinde yoğun kullanılmaktadır. 

Tabiki bu bir projede sadece NoSQL yada sadece RDBMS sistemleri tercih etmemiz gerekiyor anlamına gelmiyor. Çeşitli kriterleri göz önüne alarak detaylı incleme ve dizayn yapmamız gerekiyor. Evet bir proje sadece NoSQL bir DB ile çalışabilir, yada RDBMS bir sistem ile. Ama önemli olan anlık ve ileriye dönük ihtiyaçlara uygun bir dizayndır. Çok fazla mikroservisin birarada çalıştığı bir mimaride bazı servisleri NoSQL, bazı servisleri ise RDBMS sistemleri ile çalıştırarak karma bir veritabanı altyapısı kullanmak da mümkündür. Mikroservis mimarisinde tavsiye edilen noktalardan birisi de mümkünse her mikroservisin kendine ayrı veritabanı olmasıdır. Böylece veritabanlarından birine bakım yaptığınızda diğer mikroservisleri etkilememiş olacaksınız. Yada veritabanı çöktüğünde bütün mikroservisleriniz çökmemiş oluyor. 

Mikroservis mimarisinin getirdiği bazı dezavantajlar da yok değildir.

  • Mikroservis mimarisinin kurulduğu sistemlerde networkün düzgün ve performanslı çalışması çok önemlidir. Çünkü olay mikroservislerin, müşterilerin ve veritabanlarının network üzerinde birbirilerinin konuşması üzerine kurgulanmıştır. Network sistemleri üzerinde yapılması gerekebilecek iyileştirmeler ek maliyetlere neden olabilir.
  • Bir mikroservisin doğru çalıştığının test edilmesi monolitik yapıda yapılan testlere göre daha karmaşık olabilir. Özellikle entegrasyon testleri. Servisin test edilmesi için konuştuğu diğer servisler ile  birlikte test edilmesi gerekecektir. İyi bir otomasyon ile bu işin altından kalkılabilir.
  • Mikroservis mimarilerinde otomasyonun daha etkin kullanımı ve bu konuda ekstra efor gerekir.
  • Veri tutarlılığını sağlayabilmek için çok iyi bir dizayn ve ekstra efor gerekecektir.
  • Mikroservis mimaride güvenlik konusunda daha fazla efor ve bütçe sarfetmemiz gerekecektir. Çünkü mikroservisler birbirleri ile ve veritabanları ile haberleşirken monolitik yapılara göre daha fazla saldırılara müsait haldedir. Çünkü ağ üzerinde çok fazla noktadan akan veri trafiği mevcuttur.
  • Servis ve veritabanı sayısı arttıkça monitoring işlemi daha maliyetli ve karmaşık hale gelebilir.


Biraz da Mikroservisler ve Python hakkında konuşalım. 

Python ile mikroservis oluşturmak oldukça kolaydır. Hedeflediğiniz proje ile ilgili kriterleri karşılayacak çeşitli kütüphane ve frameworkler ile performanslı mikroservis uygulamaları geliştirmeniz mümkündür.

Hadi gelin birkaç dakikada ayağa kaldırabileceğimiz basit bir mikroservis yazalım. Bu konuyu anlamanıza daha da yardımcı olacaktır. 

Yazacağımız bu mikroservis bir veri kaynağından istenen verileri müşterilere sağlayan bir uygulama olsun. Burada veri kaynağı herhangi bir şey olabilir. Bir veritabanı, json dosyası, xml dosyası, csv dosyası, başka bir microservis... 

Biz burada veri kaynağı olarak bir json dosyası kullanacağız. Bu json dosyamız içerisinde kullanıcı bilgileri içerecek ve istendiğinde mikroservisimiz bu json dosyasını okuyarak müşteriye sunacak.

Dosyamızın adı jobs.json olsun. Bu dosyayı ana proje klasörümüzde data/ klasörü altına koyalım.

{
"acan": {
"id":1,
"name":"Ali Can",
"job":"Muhendis",
"tel":"034275654235"
},
"msert": {
"id":2,
"name":"Mehmet Sert",
"job":"Yonetici",
"tel":"03567567234235"
},
"cselman": {
"id":3,
"name":"Cem Selman",
"job":"Muhendis",
"tel":"03423867864235"
},
"malan": {
"id":4,
"name":"Murat Alan",
"job":"Mimar",
"tel":"03429455674235"
}
}

İkinci oluşturacağımız dosyamız ise jobs.py isimli bir python dosyası olsun ve ana klasörde services/ klasörü altında bulunsun.


Burada flask frameworkü bulunduğumuz python environment a yüklememiz gerekiyor. 
Bunun için öncelikle bir virtual environment yaratıp aktive edelim ve içine girip flask ı yükleyelim.
Eğer PyCharm ile geliştirme yapıyorsanız PyCharm IDE settings içinden flaskı hızlıca yüklemeniz de mümkündür.

Virtual Environment için kullandığınız işletim sistemine göre alttaki komutları çalıştıralım:

Linux >

$ mkdir myproject
$ cd myproject
$ python3 -m venv venv

Windows>

> mkdir myproject
> cd myproject
> py -3 -m venv venv

Sonrasında Virtual Environment aktivasyonunu yapalım:

Linux> 

$ . venv/bin/activate

Windows>

> venv\Scripts\activate

Son olarak Flask ı yükleyelim:

$ pip install Flask

Eğer yazılım geliştirme için PyCharm kullanıyorsanız yukarıdaki işlemleri PyCharm IDE üzerinden birkaç butona tıklayarak otomatik yaptırabilirsiniz.

Gelelim kodumuza. Alttaki kodumuzda database olarak json dosyamızı verdik ve dosyayı açarak job_list e atadık. Artık veriler job_list içinde elimizde. Şimdi bu verileri isteyenlere sunmamız gerekiyor. Bu noktada yazacağımız fonksiyonlar ile flask framework ün gücünü kullanarak elimizdeki verileri sunacağız.

Öncelikle hello() fonksiyonu ile mikroservisimizin ana istek sayfasına istek yapanlara mikroservisimizin ayakta olduğu cevabını döneriz.

Sonrasında show_lists() fonksiyonumuz ile elimizdeki tüm datayı istek yapanlara gösteririz.

Son olarak user_list() fonksiyonumuz ile ise elimizdeki datanın yanlızca belli bir bölümünü isteyen müşteriye gösteririz.

Böylece mikroservisimiz istek yapan müşterilere hangi veriyi istiyorlar ise onu dönecektir.

Alttaki 3 satır isteği yaparken hangi seçeneklerimiz olduğunu belirlememize yardımcı olar. Hangi şekilde çağırırsak ilgili fonksiyon cevap dönecektir.
@app.route("/", methods=["GET"])
@app.route("/lists", methods=["GET"])
@app.route("/lists/<user>", methods=["GET"])
Alttaki satır hangi port ile mikroservisimizin hizmet vermeye başlayacağını belirler.
app.run(port=6111, debug=True)
Yani mikroservisimizi çalıştırdığımızda alttaki şekillerde çağırabileceğiz.

  • http://127.0.0.1:6111/
  • http://127.0.0.1:6111/lists
  • http://127.0.0.1:6111/lists/msert

Kodumuzun tamamını içeren Jobs.py dosyamız.
from flask import Flask, jsonify, make_response
import json
import os

app = Flask(__name__)

database_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
file = "{}\data\\jobs.json".format(database_path)
with open(file, "r") as jsf:
job_list = json.load(jsf)


@app.route("/", methods=["GET"])
def hello():
"""Welcome Screen"""

return "JOBS Service Running"


@app.route("/lists", methods=["GET"])
def show_lists():
"""LIST"""

users_lists = []
for user in job_list:
users_lists.append(job_list[user])
return jsonify(lists=users_lists)


@app.route("/lists/<user>", methods=["GET"])
def user_list(user):
"""Get User Based Info"""

if user not in job_list:
return "Not Found"
return jsonify(job_list[user])


if __name__ == "__main__":
app.run(port=6111, debug=True)

Şimdi web browserdan mikroservisimizi çeşitli urller ile çağırıp çıktılarına bakalım.










Ve son olarak da kodumuzda tanımlanmamış bir URL nin hata vereceğini görüyoruz.


Evet Python da bir mikroservis oluşturmak bu kadar basit. Tabiki bu işleyişi göstermek açısından basit bir  örnekti. Umarım aklınızda birşeyler canlandırabilmişimdir. Burada 6111 nolu port üzerinden isteklere karşılık veren bir mikroservis yazmış olduk. Farklı bir port üzerinden çalışan onlarca mikroservis yazıp hepsini aynı ortamda çalıştırıp birbirleri ile ve veritabanları ile konuşturduğumuzda basit bire mikroservis mimarisine sahip olmuş oluyoruz.

Burada kullandığımız Flask framework çok sık kullanılan ve mikroservis oluşturmada kullanılabilecek bir framework. Python bunun gibi başka framework ve kütüphaneler de sunmaktadır. 

  • Django Rest framework çok daha karmaşık geliştirmeler yapabileceğiniz ve yine mikroservisler oluşturabileceğiniz bir yapı olarak karşımıza çıkmakta ve büyük projelerde yoğunlukla kullanılmaktadır. 
  • Yine minimalist yapıda olan python Fast API ve Falcon Web Framework hızlı bir şekilde yüksek performanslı mikroservisler yazmak isteyenlerin tercihi olmaktadır. 
  • Bunların yanısıra popüler frameworkler arasından Connexion, Hug, Eve, Bottle, Turbo Gears, Tornado ve Cornice'i sayabiliriz. 
  • Daha farklı alanlarda yapacağınız çalışmalar için de microservis geliştirebileceğiniz kütüphaneler bulunmaktadır. Bunlardan ilginç olanlarından biri ise ArcGIS API dir. Haritalama, mekansal analiz, veri bilimi, yapay zeka ve otomasyon için güçlü bir Python kütüphanesi sağlamaktadır. 
  • Hızlı, ölçeklenebilir, güvenli ve taşınabilir veri tabanına dayalı web tabanlı uygulamaların hızlı bir şekilde geliştirilmesi için ise Web2PY i tavsiye edebiliriz. Web2PY nin avantajlarından birisi, veritabanı soyutlama katmanının, SQLite, MySQL, PostgreSQL, Oracle, DB2, MSSQL, MongoDB ve birçok popüler veritabanını destekleyen bir dizi sürücü ile birlikte çalışmasıdır. 

Piyasada bunlar gibi daha pek çok spesifik işte kullanılabilecek ve mikroservisler üretebilecek kütüphane ve framework bulmanız mümkündür.

Evet bu yazımızda mikroservis mimarisine ve python ile mikroservis oluşturulması konularına giriş yaptık. İlerleyen yazılarımızda birkaç mikroservisten oluşan python kodları geliştirerek daha kapsamlı projeler yapacağız. Ayrıca yukarıda bahsettiğimiz python ile mikroservis oluşturabileceğimiz birkaç ürünü deneyip, farklı ürünlerle farklı mikroservisler hayata geçirerek birbirleri ile, çeşitli veritabanları ile ve müşteriler ile konuşmalarını sağlayacağız.

Teşekkürler,
Cem Selmanoğulları

27 Ocak 2022 Perşembe

Dependency Injection ve Dependency Inversion - PYTHON

erhaba,

Bugünkü yazımda Dependency Injection ve Dependency Inversion konularını inceleyip python ile örnek kodlar yazmaya çalışacağım.

Birçok kaynakta karışık bir şekilde anlatılan bu yapıları bakalım herkesin anlayabileceği bir şekle sokabilecek miyiz...

Dependency Inversion konsepti yazılımda S.O.L.I.D prensiplerinde tanıtılmaktadır ve bu kelimenin son harfi olan D ile gösterilmektedir. Dependency Injection ise Dependency Inversion uygulanma tekniklerinden biri olarak tanımlanabilir. Amaç diğer sınıflara bağımlılığı en aza indirecek bir yapı kurmaktır.



Bir memeli sınıfımız olsun. Bu sınıf içinde başka bir hayvan sınıfını kullanıyorsak, burda bir bağımlıktan söz etmek mümkündür. Yani memeli sınıfı hayvan sınıfına bağımlıdır. Bu bağımlılığı azaltabilmek için öncelikle dependency injection uygulanması gerekir. Alttaki kodlarımızda kısaca şunu yapacağız. Hayvan sınıfını da kullanarak bir memeli oluşturacağız. Memeli sınıfından oluşturulan nesne ye metodu aracılığı ile komut verip yürüteceğiz ve durduracağız.

class hayvan():
def yuru(self):
print("Yuru...Yuruyor.")

def dur(self):
print("Dur!...Durdu.")

class memeli():
def __init__(self):
self.hayvan = hayvan()
self.durum = False

def komut_ver(self):
if self.durum:
self.hayvan.yuru()
self.durum = False
else:
self.hayvan.dur()
self.durum = True

kedi = memeli()
kedi.komut_ver()
kedi.komut_ver()
kedi.komut_ver()
kedi.komut_ver()
kedi.komut_ver()
Çıktı :

Dur!...Durdu.
Yuru...Yuruyor.
Dur!...Durdu.
Yuru...Yuruyor.
Dur!...Durdu.

Dependency Injection bağımlılıkların dışardan alınması şeklinde sağlanmaktadır. Şimdi dependency injection ile üstteki kodumuzu alttaki şekilde düzenleyelim.

 
class hayvan():
def yuru(self):
print("Yuru...Yuruyor.")

def dur(self):
print("Dur!...Durdu.")

class memeli():
def __init__(self, memeliHayvan: hayvan):
self.memeliHayvan = memeliHayvan
self.durum = False

def komut_ver(self):
if self.durum:
self.memeliHayvan.yuru()
self.durum = False
else:
self.memeliHayvan.dur()
self.durum = True

kedi = hayvan()
sonic = memeli(kedi)

sonic.komut_ver()
sonic.komut_ver()
sonic.komut_ver()
sonic.komut_ver()
sonic.komut_ver()
Çıktı :

Dur!...Durdu.
Yuru...Yuruyor.
Dur!...Durdu.
Yuru...Yuruyor.
Dur!...Durdu.

Kodun yeni halini incelersek hayvan sınıfından üretilen nesne memeli sınıfına dışarıdan verilmektedir. Yani memeli sınıfı içinde hayvan sınıfı kullanılarak bir nesne üretimi yapılmamaktadır. Bu bağımlılığı dependency injection ile kaldırmış olduk.

Peki işi biraz ileriye götürürsek, memeli sınıfına gönderilecek hayvanları iki ayaklı ve dört ayaklı olarak ikiye ayırıp bunlar için birer ara sınıf oluşturmak istersek bunu nasıl yöneteceğiz. Burada iki ekstra sınıftan bahsediyoruz ama onlarca sınıf olabilir. Peki iki ayaklı ve dört ayaklı şeklinde ayarlayacağımız iki sınıftan üretilen hayvanları memeli sınıfına gönderebilir miyiz ? Yukarıdaki örnekte hayvan sınıfından üretilen hayvan nesnelerini memeli sınıfına gönderecek şekilde ayarlamalarımızı yaptık. Peki iki ayaklı hayvan nesnesi ve dört ayaklı hayvan sınıf nesnelerini memeli sınıfına nasıl gönderebiliriz ? Bunu direkt olarak yapmamız mümkün değildir, yani bu nesneler memeli sınıfı tarafından algılanamayacaktır. Çünkü memeli sınıfı hayvan sınıfı nesnelerini algılamak üzere geliştirildi.

Bunu interface kullanımı yardımı ile çözeriz. iki ayaklı hayvan ve dört ayaklı hayvan sınıfları bu interface'den implement alırsa sorunumuz çözülecektir. Bu durumda hayvan sınıfımız bir interface olacak. Memeli sınıfı hayvan sınıfından nesneleri kabul edecek şekilde ayarlanacak. İki ayaklı hayvan ve dört ayaklı hayvan sınıfları bu interface'den implement alarak bunlardan üretilen nesneler memeli sınıfına gönderilebilecek.

Şimdi bu senaryoya göre dependency inversion uygulayalım.

Tekrar özetlersek. Hayvan isimli bir interface olacak. İki ayaklı hayvan ve Dört ayaklı hayvan isimli birer sınıf bu interfaceden implement alacak ve bu sınıflardan üretilecek nesneler memeli sınıfına gönderilerek memeli metodu olan komut_ver() çağırılacak.


from abc import ABC, abstractmethod

class hayvan(ABC):
@abstractmethod
def yuru(self):
pass

@abstractmethod
def dur(self):
pass

class iki_ayakli_hayvan(hayvan):
def yuru(self):
print("İki Ayaklı Yuru...Yuruyor.")

def dur(self):
print("İki Ayaklı Dur!...Durdu.")

class dort_ayakli_hayvan(hayvan):
def yuru(self):
print("Dort Ayaklı Yuru...Yuruyor.")

def dur(self):
print("Dort Ayaklı Dur!...Durdu.")

class memeli():
def __init__(self, memeliHayvan: hayvan):
self.memeliHayvan = memeliHayvan
self.durum = False

def komut_ver(self):
if self.durum:
self.memeliHayvan.yuru()
self.durum = False
else:
self.memeliHayvan.dur()
self.durum = True

kedi = dort_ayakli_hayvan()
sonic = memeli(kedi)

maymun = iki_ayakli_hayvan()
jimny = memeli(maymun)

jimny.komut_ver()
jimny.komut_ver()
jimny.komut_ver()
print()
sonic.komut_ver()
sonic.komut_ver()
sonic.komut_ver()
Çıktımız :

İki Ayaklı Dur!...Durdu.
İki Ayaklı Yuru...Yuruyor.
İki Ayaklı Dur!...Durdu.

Dort Ayaklı Dur!...Durdu.
Dort Ayaklı Yuru...Yuruyor.
Dort Ayaklı Dur!...Durdu.

Evet böylece ilk kodumuza önce dependency injection uygulayarak sınıf bağımlılığından kurtardık. Sonrasında ise daha detaylı bir sınıf yapısı kurarak sınıf çeşitliliğimizi arttırma amacı ile kodumuzu bir interface den implement ederek inşaa ettik. Bu sayede dependency inversion uygulamış olduk. Bunun bir avantajı da artık aynı interfaceden implement eden yeni farklı ara sınıfları kodumuza eklemek ve memeli sınıfına göndermek sureti ile yeni tür nesneler yaratabileceğiz. Örneğin kırkayak adında üçüncü bir sınıfı hızlıca kodumuza ekleyebiliriz. Hemde interface ve memeli sınıflarını değiştirmeden.

Bunu farklı bir örnek tasarımı ile açıklamaya çalışalım. Bir adet Tır sınıfımız olsun, bir adet Uçak sınıfımız olsun, bir adet Otobüs sınıfımız olsun. Bir kargo lojistik firması için yazılım geliştiriyor olalım. Bu 3 sınıfı bir adet Operasyon isimli interface sınıfından implement ederiz. Bu Operasyon sınıfının Taşı isimli bir metodu olacaktır. Dolayısı ile Uçak, Otobüs, Tır sınıflarının da Taşı isimli metodu olacaktır. Bu 3 sınıftan üretilen nesneleri ana sınıfımız olan Kargo sınıfına göndereceğiz.
Kısaca  Operasyon()  ->  ( Uçak() , Otobüs() , Tır() )  ->  Kargo() şeklinde bir yapı olacaktır.
Görebileceğiniz üzere böyle bir yapısı olan kargo firması ilerleyen yıllarda bir gemi alıp gemi kargosu işlerine girerse Gemi() sınıfını sisteme dahil etmek oldukça kolay olacaktır ve bu ekleme diğer sınıfları etkilemeyecektir.

Teşekkürler,
Cem Selmanoğulları 










23 Ocak 2022 Pazar

Python ve Singleton Design Pattern (Singleton Tasarım Örüntüsü)

Merhaba,

Bu yazımda Singleton Design Pattern konusundan bahsederek python diliyle yazılmış bir örneği paylaşmak istiyorum. 

Tasarım örüntülerinde belirli bir problem için belirli bir çözüm sunulmaktadır. Bu sayede tasarım iyileştirilir ve takımın aynı dili konuşması sağlanır. Tasarım örüntüleri belirli bir işi yapmak yada çözüme ulaşmak için en iyi yöntemi sunar. Çatısı belirli yazılım örüntüleri kullanılarak oluşturulan yazılımların ilerleyen süreçlerde büyümesi, bakımının yapılması ve devamlılığının sağlanması daha kolay olacaktır.

Her tasarım örüntüsünün bir ihtiyaçtan yada bir sorunu çözme amacından ortaya çıktığını söyleyebiliriz. Singleton da bunlardan biridir. Creational Design Pattern (Yaratımsal Tasarım Örüntüsü) grubuna dahildir.



Bu tasarım örüntüsü çalışma zamanında yanlızca 1 obje yaratılmasını garanti eder. İkinci bir obje yaratılamayacaktır ve sadece bu obje çağırılıp kullanılacaktır. Kısaca tüm uygulama için bu sınıftan yanlızca bir nesne olacaktır. Bu nesneye ihtiyaç olduğunda her yerde aynı tek örnek çağırılacaktır. Böylece bir instance'a kontrollü erişim sağlayabiliriz. Singleton'da bir obje sadece ona ihtiyaç duyduğumuzda yaratılır. 

Bir sınıftan bir nesne oluşturabilirsiniz ve bu nesneyi program içinde değişik yerlerde defalarca kullanmak isteyebilirsiniz. Örneğin bir veritabanı bağlantı nesnesini bir kere oluşturup her veritabanı işlemi gerektiğinde bu nesneyi kullanabilirsiniz. Eğer burada Singleton deseni ile geliştirme yaptıysanız aynı veritabanı bağlantı sınıfından ikinci bir nesne üretilemeyecek ve her seferinde istek yapıldığında ilk yaratılan nesne örneği gelecek ve kullanılacaktır. Ayrıca bir nesneyi global bir değişkende tutmak da sıkıntılıdır, çünkü kodun herhangi bir yerinde o değişkenin üzerine yazılarak tüm kodun çökme ihtimali vardır. Singleton nesneyi global bir değişkene atamadan global bir şekilde istediğimiz yerde çağırıp kullanabilmemize izin verir. Böylece global bir kullanım sunarken başka bir kodun nesne üzerine yazması da engellenmiş olur. Kod Singleton'un statik metoduna erişebiliyorsa bu metod her çağırıldığında aynı nesne örneğinin getirilmesi sağlanacaktır.

Burada önemli noktalardan birisi de şudur. Singleton ile global değişkenler üzerinde daha sıkı bir kontrol sahibi olmuş oluruz. Singleton sınıfının kendisi dışında hiçbir kod parçası nesne örneğini değiştiremez.

Biraz da kodlar üzerinden anlamaya çalışalım. Öncelikle bir sınıf oluşturup bu sınıf ile sadece bir adet nesne üretilmesini garanti edecek basit bir kod parçacığı yazmak istiyorum.


class Singleton:
__instance = None

def __init__(self):
""" Virtually private constructor. """
if Singleton.__instance != None:
raise Exception("This class is a singleton!")
else:
Singleton.__instance = self

singObj1 = Singleton()
print(singObj1)

Yukarıdaki kodda sınıf üzerinden bir nesne yaratıyoruz. Nesne yaratıldığında sınıf içindeki __instance değişkenini kontrol ederek daha önce bu sınıftan bir nesne yaratılıp yaratılmadığını kontrol ediyoruz. Eğer daha önce nesne yaratılmadıysa nesnemiz yaratılıyor ve sınıf içindeki __instance değişkenine nesnemiz atanıyor. Ekrana bastırdığımızda da alttaki şekilde hatasız bir şekilde nesnenin yaratıldığını görüyoruz.

<__main__.Singleton object at 0x0000024535C47C10>

Şimdi alttaki şekilde ikinci bir obje yaratmaya çalışalım.


class Singleton:
__instance = None

def __init__(self):
""" Virtually private constructor. """
if Singleton.__instance != None:
raise Exception("This class is a singleton!")
else:
Singleton.__instance = self

singObj1 = Singleton()
print(singObj1)
singObj2 = Singleton()
print(singObj2)

İlk nesnenin yaratılmasına kadar herşey aynı. Artık sınıf içindeki __instance değişkenine nesnemiz atanmış durumda. İkinci kes bu sınıftan nesne üretmeye çalıştığımızda ise yine __init__ içindeki kontrol yapılacaktır. Bu sefer __instance değeri None olmayacaktır. Bu sebeple "This class is a singleton!" şeklinde bir exception fırlatacaktır. Burda dikkat ederseniz nesneyi global bir değişkende tuttuk.

Exception: This class is a singleton!

Bu örneği temel mantığı anlamanız için yaptık. Şimdi biraz daha kapsamlı bir örnek paylaşmak istiyorum. Bu örnekte singleton sınıfından bir nesne üreteceğiz ve global bir değişkene bu nesneyi atamadan her istediğimizde sınıfın metodu ile çağıracağız. Böylece nesneyi global değişkende tutarak çağırmanın getireceği risklerden de kurtulmuş olacağız. Birçok yerde paylaşılan standart bir örneği olduğu gibi paylaşarak açıklamak istiyorum.


class SingletonMeta(type):
"""
The Singleton class can be implemented in different ways in Python. Some
possible methods include: base class, decorator, metaclass. We will use the
metaclass because it is best suited for this purpose.
"""

_instances = {}

def __call__(cls, *args, **kwargs):
"""
Possible changes to the value of the `__init__` argument do not affect
the returned instance.
"""
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]


class Singleton(metaclass=SingletonMeta):
def some_business_logic(self):
"""
Finally, any singleton should define some business logic, which can be
executed on its instance.
"""
# ...


if __name__ == "__main__":
# The client code.

s1 = Singleton()
s2 = Singleton()
print(s1)
print(s2)

if id(s1) == id(s2):
print("Singleton works, both variables contain the same instance.")
else:
print("Singleton failed, variables contain different instances.")

Bu kod örneğimizde sınıfımız içinde __call__ metodunun kullanıldığını göreceksiniz. Bu metod sınıfımızdan üretilecek nesne örneğinin istenildiği bir anda bir fonksiyon gibi çağırılarak getirilebilmesini sağlar.

Yani Singleton sınıfımızdan üretilecek nesne örneğini Singleton() ile defalarca çağırabiliriz ve her seferinde aynı nesne örneği gelecektir ve ikinci bir nesne oluşturulmayacaktır. Bu tamda Singleton Design Pattern'de istediğimiz şey.

Yine burada _instances isimli bir değişkenimiz var. Bu değişken sınıf ilk nesneyi ürettiğinde oluşturulan nesne örneğini tutacak ve her çağırıldığında aynı örneği verecektir. Kontrol __call__ metodu içinde yapılacak eğer daha önce nesne üretilmediyse nesne üretilecek ve ikinci kes nesne üretilmesine izin verilmeyecektir.

Koda bakarsanız s1 ve s2 de iki kere bu sınıf nesnesi çağırılmış. Peki her seferinde aynı nesne mi gelmiş ? Evet. Aşağıdaki çıktıya baktığınızda aynı nesne örneğinin geldiğini göreceksiniz. Singleton() çağrısını yüzlerce kez de yapsak artık hep aynı nesne örneği gelecektir.

<__main__.Singleton object at 0x000002B5D5F4FF70>

<__main__.Singleton object at 0x000002B5D5F4FF70>

Singleton works, both variables contain the same instance.


Teşekkürler,

Cem Selmanoğulları

22 Ocak 2022 Cumartesi

Grafana nedir ? Veri görselleştirme. Dashboard oluşturma.

Merhaba,

Bugün son yılların popüler opensource uygulamalarından biri olan ve Grafana Labs tarafından sunulan Grafana'dan bahsetmek istiyorum. Bu yazımda Grafana'yı tanıtmaya yönelik giriş seviyesi bilgiler vermeye çalışacağım. Daha sonraki yazılarımda ise daha derinlemesine inceleme ile ürün yönetimine dair konulara değinerek detaylı dashboardlar oluşturmayı planlıyorum.

"Dashboard anything. Observe everything."

Web tabanlı veri görselleştirme ve raporlama uygulaması olarak özetleyebileceğimiz bu güçlü uygulama pekçok büyük firma tarafından kullanılmaktadır. Şirketler altyapılarını izlemek ve operasyonel verimliliklerini arttırmak için Grafana'yı kullanmaktadır. Verilerin toplanması, yönetilmesi ve görüntülenmesini otomatikleştirdiği için izleme ve analizi oldukça kolaylaştırır. Bölüm ve takım yöneticileri, analistler, mühendisler karar verme mekanizmalarında Grafana'dan sıklıkla yararlanmaktadır. Anlık durum tespiti ve ileriye dönük planlamaların yapılması anlamında doğru verilere ulaşılıp doğru kararlar verilmesinde oldukça etkin bir rol oynamaktadır. Güvenlik analizi ekipleri tarafından da kullanılabilen Grafana kullanıcıların dijital ayak izlerinin takibi konusunda da fayda sağlamaktadır. Birçok veritabanı ile entegre olup veri çekebilen uygulama gerçekten gözalıcı grafikler, tablolar ve dashboardlar oluşturabilmektedir. Grafana ile verileriniz nerede olursa olsun sorgulama, ölçme, görselleştirme ve raporlama işlerinizi yapabilirsiniz. Operasyonel ölçümleriniz ile ilgili grafik ve raporlar oluşturabilirsiniz. İşin içine Telegraf ve InfluxDB araçlarını da kattığınızda Grafana sistemini oldukça güçlü ve yönetimi kolay bir izleme sistemine çevirmeniz de mümkün.


Grafana sistemi Linux, Windows ve MacOS üzerinde çalıştırılabilmektedir. Grafana ile fiziksel ve sanal sunucu ve ağ sistemleri ile ilgili çeşitli dashboardlar oluşturabilirsiniz, raporlamalar yapabilirsiniz. Sunucular ve ağınızı izleyebilirsiniz. Cloud üzerindeki birçok servisi takip edebilirsiniz. Çeşitli uygulamaları ve loglarını takip edebilirsiniz. Anlık durum yada geriye dönük verileri izleyebileceğiniz grafikler ile trendlere bakabilirsiniz. Zaman bazlı kıyaslamalar ve trendleri izleyebileceğiniz grafikler yaratabilirsiniz. Sistemlerin performans verilerini raporlayabilirsiniz. Kapasite planlamalarınızı takip edip geriye dönük grafikler ile raporlamalar yapabilir kapasite ve kullanım trendlerini izleyebilirsiniz. Envanter bilgilerinizi takip edebilirsiniz.  

Grafana ile dakikalar içinde profesyonel dashboardlar hazırlamanız mümkün. Tabi bunun için biraz SQL bilginizin de olması gerekiyor. 

Çekilecek veriler için SQL sorguları yazmanız gerekiyor. Ama bu gözünüzü korkutmasın, çok temel SQL sorguları ile harika dashboardlar ortaya çıkarabiliyorsunuz.



Alttaki dashboard bir sunucu ile ilgili çeşitli metrikleri görselleştirmektedir.


Bir dashboard alttaki şekilde bağımsız panellerden oluşmaktadır.



Her bir panel ayrı bir SQL sorgusu sonucu oluşan verilerden görselleştirilmektedir.

Yukarıdaki MEM panelinin detayına baktığımızda alttaki şekilde iki SQL cümlesi ile oluşturulduğunu göreceksiniz.


SQL cümlesini manuel yazmak istemeyenler için ise basitleştirilmiş bir editör de sunulmaktadır.


Alttaki örnekte SQL Serverdan çekilecek veriler için yazılmış bir SQL sorgusu görmektesiniz.


Verileri panelde Time Series, yani zamana bağlı değişen grafik şeklinde gösterebildiğimiz gibi tablo olarak da çekilen verilerin ham hali ile de gösterebiliriz. 
Altta grafikle gösterilen verilerin tablo ile gösterimini görmektesiniz.


Yine altta tablo formatında bir panel görmekteyiz.


Grafana'nın sağladığı çeşitli visualization seçenekleri ile yapacağınız görselleştirmeler hayallerinize kalmış. 
Birkaç farklı visualization örneğini altta paylaşmak isterim.







Visualization seçeneklerini alttaki şekilde listeleyebiliriz.

Verileri CSV dosyası olarak indirmeniz de mümkündür.


Ayrıca herhangi bir paneli "Share" özelliği sayesinde paylaşabilirsiniz. "Share Panel" çeşitli paylaşım opsiyonları sunmaktadır.


Direkt olarak panel link url sini paylaşmanın yanısıra panelin imaj olarak oluşturulmuş resim dosyasına da ulaşmak mümkündür. 
Bunun yanısıra "Snapshot" paylaşımı ile panelin o anki hali public olarak bir link ile paylaşılacaktır. 
Bu link adresini bilen herkes bu panelin o anki görüntüsüne erişebilecektir. 

Embed özelliği sayesinde ise panel bir iframe olarak Grafana sistemi dışında başka bir web sitesinin 

kodları içine gömülebilmekte ve diğer sitelerde görüntülenebilmektedir. 

Bu işlem bildiğimiz HTML için iframe gömme işlemi ile aynıdır.


Grafana sistemi anlık ve geriye dönük olarak verileri, trend grafiklerini ve tabloları gösterebilmektedir. 
Ayrıca belli bir zaman aralığı içinde panel ve dashboardlar özelleştirilebilmektedir.


Ayrıca "Cycle View Mode" özelliği ile dashboardlar bir ekrana yansıtılarak belli periyodlarda döngüsel olarak gösterilebilmektedir.

Dashboardlar üzerinde gösterilen veriler detaylı olarak filtrelenebilmektedir. 

Oluşturulacak filtre değişkenleri birbirleri ile ilişkilendirilerek anlık olarak dashboardlar üzerindeki verileri filtrelemek mümkündür.


Filtreleme işlemleri için Dashboard ayarları içinde Variables kısmında değişken tanımlamaları yapılabilmektedir. 
Her bir değişken diğer değişken ile ilişkilendirilebilirken kendine ait bir SQL sorgusu ile eşleşmektedir.


500 tane sunucunuz ve her sunucu üzerinde 3 adet network interface iniz olduğu bir senaryoda sadece operasyon bölümüne ait sunucuların dashboard üzerinde gösterilmesini sağlayabilirsiniz. Yada sadece belli sunucuların sadece eth1 network interface kullanımlarının dashboard üzerinde gösterimi sağlanabilir.

Grafana sisteminde alarm oluşturmak da mümkündür. Belli kriterlere göre alarmlar oluşturulup kriterler gerçekleştiğinde yada belli eşikler geçildiğinde sistemin alarm üreterek mesaj göndermesi sağlanmaktadır.


Tablo ve grafik verilerinde yine treshold belirlenerek alarm durumlarının görsele yansıtılması da mümkündür.



Grafana sistemi kullanıcı ve kullanıcı grupları yönetimi ve yetkilendirilmesi konusunda da oldukça yeterli özellikler sunmaktadır. Ldap entegrasyonu yapılabilmektedir. Dashboardları oluşturacağınız kullanıcı ve takımlara göre yetkilendirerek kimlerin göreceği yada düzenleyebileceği konularında özelleştirebilirsiniz. Ayrıca dashboarları bir klasör yapsında gruplayarak yetkilendirmeleri dashboard grupları üzerinde de uygulayabilirsiniz. Ayrıca organizasyon bazlı ana ayrımlar yapmak da mümkündür. 

Grafana'nın en güzel özelliklerinden birisi ise grafana.com üzerinden daha önce hazırlanmış dashboardları import edebiliyor olmanızdır. Yani herhangi bir geliştirme yapmadan ve SQL sorgusu yazmadan size uygun hazır dashboardları ücretsiz olarak saniyeler içinde devreye alabilirsiniz.


Grafana.com üzerinden ID sini alacağınız dashboardı hemen import edip kullanmaya başlayabilirsiniz.

Örnek vermek gerekirse yüzlerce linux makine ile ilgili onlarca bilginin görselleştirildiği bir dashboardı hızlıca sisteminizde devreye alabilirsiniz. Dashboard ve panel dizaynı ile uğraşmanıza gerek olmadan.

Grafana sistemi dışarıdan web sayfalarının da dashboard panel olarak sisteme eklenmesine izin vermektedir. Buda Grafana sistemi daha da genişletebilmenize imkan vermektedir. Bir projede müşterimiz bazı scriptleri çalıştırarak çıktıları olan grafikleri Grafana üzerinde bir dashboard olarak görüntülemek istediğini belirtmişti. Normal şartlarda Grafana'da bu tarz bir tetikleyerek grafik oluşturma yapısı yok. Fakat dışarıda oluşturduğumuz bir web sayfası formatını Grafana formatına yakın bir formatta hazırlayarak sisteme entegre ederek müşterinin isteğini gerçekleştirme şansımız olmuştu.

Sıkça sorulan sorulardan biri ise Grafana üzerindeki grafik ve tablo panelleri kullanarak sıfırdan yeni bir web sitesi tasarımı yapabilir miyiz? Kendi web şablon tasarımlarını kullanmak isteyen bazı firmalardan bu şekilde istekler gelebiliyor. Teknik olarak yapılması mümkün fakat Grafana'nın kullanıcı yetkilendirme sistemini devre dışı bırakmış oluyorsunuz. Yeni oluşturacağınız web sitesinde kendi kullanıcı yetkilendirmelerinizi yaptıktan sonra neden olmasın. Paneller iframe olarak dışarıya aktarılabiliyor. Bu şekilde yeni oluşturacağınız web sitesinin içine hertürlü gömmeniz mümkün. Tabi burada Grafana tarafında bir güvenlik açığı olur mu sorusu da akla gelmektedir. Çünkü panelleri dışarıya açabilmek için anonymous kullanıcı otantikasyonunu açmanız anlamına gelmektedir. Buda Grafana'yı herkese açtığınız anlamına gelmektedir. Bunu aşmanın yolu ise Grafana sunucusu ile yeni web sitesini oluşturacağınız sunucu arasında point to point bir iletişim kurmak ve Grafana'ın web portuna sadece bu web sunucusunun erişebilmesini sağlamak. Böylece kullanıcılar yeni web sitesine erişirken Grafana sunucusuna erişemeyecek fakat Grafana'daki panelleri yeni web sunucusu görüntüleyerek kullanıcılara iletebilecektir. Bu şekilde Grafana'dan tamamen farklı bir web uygulaması hazırlanabilir ve sistemi kullananlar arka planda Grafana'nın olduğunun farkında bile olmayacaktır.

Grafana backend bir HTTP API hizmeti de sunmaktadır. Böylece farklı araçlar ile entegrasyon yada otomasyon işleri bu API aracılığı ile kolaylıkla yapılabilmektedir. Dashboard oluşturma, kaydetme, kullanıcı yaratma, takım yaratma ve yönetme, data source update, alarmların alınması, playlist oluşturulması, silinmesi, snapshot alınması ve silinmesi gibi işlemleri bu API aracılığı ile yapabilmekteyiz.

Grafana sistemi çeşitli pluginlerin kullanımı ile daha da yetenekli hale gelmektedir. CloudWatch, Elasticsearch, Google Cloud, Graphite, Jaeger, Microsoft SQL Server, MySQL, OpenTSDB, Prometheus, Tempo, Zipkin gibi pekçok datasource kullanımını mümkün kılmaktadır. Bunlar dışında alttaki data sourcelar konusunda pluginler ile desteğe sahiptir.





Grafana uygulamasını kendi sunucularınıza kurabilir yada Grafana Cloud hizmetinden faydalanabilirsiniz.

AWS tarafında Amazon'un sunduğu Amazon Managed Grafana servisi de son günlerin popüler servislerinden biri haline gelmiş durumda. AWS tarafından 2 tür lisans sağlanmakta. Editör ve Görüntüleyici lisansları. AWS fiyat listelerine baktığımızda 20 Editör ve 30 görüntüleyici lisansı için aylık olarak alttaki şekilde bir hesap çıkarıldığını görüyoruz.

Monthly Charges = 20 * $9.00 (Editor license) + 30 * $5.00 (Viewer license) = $330.00



Alttaki görsellerde AWS tarafındaki Grafana ekranlarından örnekleri görebilirsiniz.



Grafana kullanan bazı firmalara Siemens, Salesforce, SNYK, Dell, Tripadvisor, TomTom, JPMorgan, Dapper, Citi, Roblox, Unity, Redis, Wix, Booking.com, DigitalOcean, Stackoverflow, Ebay, Tinder, Verizon, Bloomberg, PayPal kuruluşlarını örnek verebiliriz.


Grafana docker imajını indirmek için TIKLAYIN.


Grafana'yı hemen denemek için Demo sayfasına gidebilirsiniz.

https://play.grafana.org/

 



Teşekkürler,

Cem Selmanoğulları