/DistributedChallenge

Bu repoda aslında asenkron mesaj kuyruklarını hedef alan bir dağıtık sistem problemi oluşturmaya ve bu problemin çözümünü uygulamaya çalışıyorum.

Primary LanguageC#

Distributed Challenge

Bu repoda aslında asenkron mesaj kuyruklarını hedef alan bir dağıtık sistem problemi oluşturmaya ve bu problemin çözümünü uygulamaya çalışıyorum. Öncelikle vakanın temel senaryosu ile işe başlamak lazım. Hangi enstrümanları ve platformları kullanacağımıza sonrasında karar verebiliriz.

Katkı Vermek İçin

Projeye katkı vermek isteyenler dev branch'inden yeni bir branch açıp ilerleyebilirler. Çalışma zamanı testlerinde bir sorun olmazsa açılan feature branch'ten dev branch'ine bir pull request açılarak ilerlenebilir.

Vaka Senaryosu

Kullanıcılarına oyun kiralayan bir internet şirketi olduğunu düşünelim. Şirketin son kullanıcılara (End Users) sunduğu web ve mobile bazlı uygulamalar dışında şirket genel merkezinde kullanılan Back Office tadında farklı bir uygulamaları daha var. Bu uygulamada yer alan ekranlardan birisi de raporlama talepleri için kullanılıyor. Şirketin sahip olduğu veri miktarı ve rapor taleplerinin belli bir onay sürecinden geçip içeriklerini farklı alanlardan toplaması nedeniyle bir raporun çekilmesi bazen birkaç dakikayı bulabiliyor. Her ne kadar şirket içerisinde bu işleri üstelenen bir raporlama ekibi bulunsa da personelin kullandığı web sayfalarında bu şekilde belirsiz süreyle beklenmeleri istenmiyor. Çözüm olarak rapor talebinin girildiği bir formun tasarlanması ve talebin raporlama ekibine ait uygulamalara ulaştırılıp hazır hale geldikten sonra personelin bilgilendirilmesi şeklinde bir yol izlenmesine karar veriliyor. Tüm bu sürecin tamamen sistem tarafından gerçekleştirilmesi ve otomatize edilmesi isteniyor.

İşte Örnek Bir Rapor İfadesi

Geçtiğimiz yıl kiraladığımız oyunlardan en çok pozitif yorum alan ilk 25 adedinin ülke bazlı satış rakamları.

Çözümleme ile İlgili Bilgiler

Buradaki senaryonun çözümü noktasında oldukça basit ilerlemeye çalışacağım. Bu amaçla tek bir solution içerisinde tüm paydaşların .Net tabanlı uygulamaları olacak. Kilit noktalardan birisi raporların hazırlanmasının uzun sürmesi ve bizim backoffice personelinin rapor talep ettiği sayfada iken bekletmek istemeyişimiz. Rapor hazırlandığında ise onu bir şekilde bilgilendirmemiz. Uygulama açıksa belki bir popup ile ve hatta ekstradan e-posta bildirimi ile. Tahmin edileceği üzere aynı Solution içerisinde bir çözümle yapacak olsak da çözüme dahil olan Process'ler network üzerinde farklı lokasyonlardaki Node'larda çalışıyor olabilirler.

Aday Çözüm

Bu problemi aşağıdaki gibi çözmeye çalıştığımızı düşünelim.

Challenge Solution Candidate

Senaryodaki adımları da aşağıdaki gibi tarifleyelim.

  1. CEO'muz geçtiğimiz yıl en çok pozitif yorum alan oyunlardan ilk 50sini ülke bazında satış rakamları ile birlikte talep eder. Bu talebi web uygulamasındaki forma girer.
  2. Bu rapor talebi web form üstünden kaydedildiğinde şimdilik Event Trigger Service olarak adlandırılan ve başka bir process'de yer alan bir servis tetiklenir. Servise formdaki veriler benzersiz bir ID ile (işlemleri tüm süreç boyunca benzersiz bir GUID ile takip edebilmek için) damgalanarak POST metoduyla gönderilir.
  3. Event Trigger Service'in tek işi gelen içeriği ReportRequestedEvent isimli bir olay mesajı olarak hazırlayıp kuyruğa bırakmaktır.
  4. Şimdilik Event Consumer/Publisher Gateway(Gamersworld.EventHost) diye adlandırdığımız başka bir process olayları dinlemek ve bazı aksiyonlar almakla görevlidir. ReportRequestedEvent isimli olayları dinleyen thread'leri vardır.
  5. Event Consumer/Publisher Gateway(Gamersworld.EventHost) servisi bir ReportRequestedEvent yakaladığında Reporting App Service(Kahin.Gateway) isimli bir başka servise POST çağrısı gönderir. Reporting App Service(Kahin.Gateway)'in gelen rapor taleplerini toplayan bir başka process olduğunu ifade edebiliriz.
  6. İç çalışma dinamiğini pek bilmediğimiz Reporting App Service(Kahin.Gateway) belli bir zaman diliminde raporun hazırlanmasından sorumludur. Hazırlanan raporu kendi Local Storage alanında saklar ve hazır olduğunda bunun için şimdilik External Reader Service(GamersWorld.Gateway) olarak adlandırılan ve kendi Process'i içinde çalışan bir diğer servise POST bildiriminde bulunur.
  7. External Reader Service(GamersWorld.Gateway), raporun hazır olduğuna dair ReportReadyEvent isimli yeni bir olay mesajı hazırlar ve bunu kuyruğa bırakır.
  8. Event Consumer/Publisher(Gamersworld.EventHost) tarafındaki Process ReportReadyEvent isimli olayları dinler.
  9. Event Consumer/Publisher(Gamersworld.EventHost) tarafında ReportReadyEvent yakalandığında yine farklı bir process'te çalışan Reporting File Service hizmeti GET ile çağrılır ve üretilen rapora ait PDF çıktısı çekilir.
  10. Servisten çekilen PDF içeriği bu sefer Back Office uygulamasının bulunduğu ağ tarafındaki local storage'e aktarılır. Aynı anda bu sefer ReportIsHereEvent isimli bir başka olay mesajı kuyruğa bırakılır.
  11. Kendi process'i içinde çalışan Report Trace Service isimli servis uygulaması ReportIsHereEvent olayını dinler. (Bunu da belki Event Consumer/Publisher(Gamersworld.EventHost) isimli Host uygulamasında ele alabiliriz)
  12. Report Trace Service hizmeti, ReportIsHereEvent isimli bir olay yakaladığında Local Storage'a gider ve ilgili PDF'i çeker.
  13. Rapor artık hazırdır. Rapor e-posta ile CEO'ya gönderilir ve Local Storage ortamlarında gerekli temizlikler yapılır.

Senaryoda dikkat edileceği üzere bazı ihlal noktaları da vardır. Örneğin Form uygulamasından girilen rapor talebindeki ifade geçersiz olabilir. Sistemden bilgi sızdırmaya yönelik güvensiz ifadeler içerebilir vs. Bu gibi bir durumda SystemMiddleEarth'nin geçersiz bir mesaj olduğuna dair SystemHome'ü bilgilendirmesi de gerekir. SystemMiddleEarth'nin kendi içinde ele alıp detay loglarla değerlendirdiği bu durum SystemHome'e bir Event olarak girer ve buna karşılık da bir süreç başlayabilir. Resimde görülen soru işaret kısmı dikkat edileceği üzere hata durumu yakalandıktan sonra ne yapılacağına dairdir. Rapor talebi sahibinin bilgilendirilmesi için e-posta gönderimi, sistem loglarına bilgi düşülmesi, form uygulamasında popup ile bilgilendirme yapılması vs gibi bir işler bütünü başlatılabilir.

Solution için Aday Uygulama Türleri

  • Rapor formu girilen arabirim basit bir Asp.Net MVC uygulaması olabilir.

  • Rapor taleplerine ait olayları oluşturup gönderen Event Trigger Asp.Net Web Api olabilir.

  • Asenkron mesaj kuyruğu için RabbitMQ kullanılabilir. İşi kolaylaştırmak adına RabbitMQ bir **Docker Container **ile işletilebilir.

  • Event Consumer/Publisher Service(Gamersworld.EventHost) tarafı esas itibariyle rapor talebi, rapor alındı ve rapor hazır gibi aksiyonları sürekli dinleyen ve farklı aksiyonları tetikleyen bir ara katman gibi duruyor. Sürekli çalışır konumda olan bir process olması ve farklı gerçekleşen aksiyonlar için başka bağımlılıkları tetikleyebilecek bir yapıda tasarlanması iyi olabilir. Bu anlamda sürekli çalışan dinleyici ve aksiyonları gerçekleştiren bir kütüphane topluluğuna ihtiyaç duyabiliriz. Yani host uygulamanın hangi olay gerçekleştiğinde hangi aksiyonları alacağını belki bir DI Container üstünden çözümleyerek çalışıyor olması gerekebilir. Bu durumda üzerinde durduğumuz bu Challenge içerisindeki başka bir Challenge olarak karşımıza çıkıyor.

  • External Reader Service(GamersWorld.Gateway) aslında Reporting App Service(Kahin.Gateway)'in tüketilmesi için açılmış bir Endpoint sağlayıcı rolünde. Onu da ayrı bir Web API hizmeti olarak tasarlayabiliriz.

  • Reporting App Service(Kahin.Gateway) ve Reporting File Service birer Web API hizmeti olarak tasarlanabilirler. Local Storage olarak senaryoyu kolayca uygulayabilmek adına fiziki dosya sistemini kullanacaklarını düşünebiliriz. Yani hazırlanması bir süre alacak PDF içeriğini fiziki diske kaydetip buradan okuyarak External Reader Service(GamersWorld.Gateway)'e iletebilirler.

  • Report Trace Service uygulaması da esasında sürekli ayakta olması ve ReportIsHere olayını dinlemesi gereken bir konumadır. Bu anlamda sürekli çalışan bir Console uygulaması olarak da tasarlanabilir.

    Not: Dikkat edileceği üzere Event Consumer/Publisher(Gamersworld.EventHost) olarak araya koyduğumuz katman bazı olaylar ile ilgili olarak bir takım aksiyonlar alıyor. Örneğin ReportRequestEvent gerçekleştiğinde POST ile bir dış servise talep gönderiyor ve ReportIsHereEvent gerçekleştiğinde de GET Report ile PDF/CSV/XML/JSON çekip Back Office tarafındaki Local Storage'a kaydediyor. Dolayısıyla bir event karşılığında bir takım aksiyonlar icra ediyor. Bunları A event'i için şu Interface implementasyonunu çağır şeklinde başka bir katmana alarak runtime'da çözümlenebilir bağımlılıkar haline getirebilir ve böylece runtime çalışıyorken yeni event-aksiyon bağımlılıklarını sisteme dahil edebiliriz belki de. Dolayısıyla aradaki Event Consumer/Publisher(Gamersworld.EventHost)'ın tasarımı oldukça önemli.

Yapılacaklar Listesi (ToDo List)

  • Solution yapısı ve proje isimlendirmeleri gözden geçirilebilir.
  • Solution için Sonarqube entegrasyonu yapılıp kod kalite metrikleri ölçümlenebilir.
  • Bazı kütüphaneler için birim testler (Unit Tests) yazılarak Code Coverage değerleri yükseltilebilir.
  • Kahin (SystemMiddleEarth) sistemindeki projeler için ayrı bir Solution açılabilir.
  • Loglama altyapısı Elasticsearch'e alınabilir.
  • Messenger servisi gRPC türüne evrilebilir.
  • Bazı Exception durumları için Custom Exception sınıfları yazılabilir. Ancak servis tarafında Exception döndürmek yerine hata ile ilgili bilgi barındıran bir Response mesaj döndürülmesi daha iyi olabilir.
  • Daha önceden çekilmiş raporlar için tekrardan üretim sürecini başlatmak yerine Redis tabanlı bir caching sistemi kullanılabilir.
  • Tüm servisler HTTPS protokolünde çalışacak hale getirilebilir.
  • Uçtan uca testi otomatik olarak yapacak bir RPA (Robotik Process Automation) eklentisi konulabilir. Belki otomatik UI testleri için Playwright aracından yararlanılabilir.
  • Bazı servislerin ayakta olup olmadıklarını kontrol etmek için bu servislere HealthCheck fonksiyonları eklenebilir.
  • URL adresleri, RabbitMQ ortam bilgileri (Development, Test, Pre-Production, Production) gibi alanlar için daha güvenli bir ortamdan (Hashicorp Vault, AWS Secrets Manager, Azure Key Vault, Google Cloud Secret Manager, CyberArk Conjur vb) tedarik edilecek şekilde genel bir düzenlemeye gidilebilir.
  • Log mesajları veya Business Response nesnelerindeki metinsel ifadeler için çoklu dil desteği (Multilanguage Support) getirilebilir.
  • SystemHome'daki Event Host uygulaması bir çeşit Pipeline. Event yönetiminde ilgili business nesneler çağırılmadan önce ve sonrası için akan veri içeriklerini loglayacak ortak bir mekanizma yazılabilir.
  • ...

Runtime (Standart)

Çalışma zamanı yapılan geliştirmelerin test koşumları için önemlidir. Üzerinde çalıştığımız çözüm birden fazla proje ve çalışma zamanı içerdiğinden test koşumları ilk etapta manuel olarak tesis edilmiştir. Bu nedenle biraz zorlayıcı olabilir. Minik bir kontrol listesi işe yarayabilir.

Not: Buradaki ve çözüme sonradan eklenecek uygulamaları tek seferde çalıştırmak için bir shell script dosyamız var. run_all.sh isimli dosyayı bu amaçla kullanabilirsiniz.

  1. RabbitMQ'nun çalışır halde olduğu kontrol edilir. (localhost:15672)
  2. System MiddleEarth'deki Kahin.ReportingGateway servisi çalıştırılır. (localhost:5218)
  3. System Home'de yer alan GamersWorld.Messenger servisi çalıştırılır. Web uygulaması taleplerini iletmek için bu servisi kullanır. (localhost:5234)
  4. RabbitMQ event'lerini dinleyen GamersWorld.EventHost console uygulaması çalıştırılır.
  5. Rapor ifadesini denetleyen System HAL sistemindeki Eval.AuditApi servisi çalıştırılır. (localhost:5147)
  6. Rapor talebi girdisi yapılan GamersWorld.WebApp çalıştırılır. (localhost:5093)
  7. builder.Logging.ClearProviders(); builder.Logging.AddConsole(); tarafındaki event streaming'leri dinleyen Kahin.EventHost isimli Console uygulaması çalıştırılır.

Bu durumda web uygulamasından örnek bir raporu girilip gönderildiğinde diğer uygulamalarda aşağıdakine benzer log bilgilerinin oluşması beklenir.

Runtime_01

Runtime_02

Runtime_03

Runtime_04

Runtime_05

Runtime (Docker Compose Senaryosu)

Nihai senaryonun işletilmesinde birçok servisin aynı anda ayağa kalkması gereken durumlar söz konusu olacak. Örneğin SystemMiddleEarth tarafındaki raporlama hizmetleri ile SystemHome tarafındaki bazı hizmetler birer REST servisi gibi konuşlanabilirler. Asenkron mesajlaşma kuyruğu içinse RabbitMQ kullanmak oldukça mantıklı görünüyor. Event'leri dinleyen Consumer tarafı da bir Console uygulaması. Tüm bunların aynı anda çalıştırılması noktasında Docker Compose' dan yararlanılabilir. Docker-Compose'a dahil olacak her projede birer Dockerfile yer alıyor.

# Docker-Compose'u sistemde build edip çalıştırmak için
sudo docker-compose up --build

# Sonraki çalıştırmalarda aşağıdaki gibide ilerlenebilir
sudo docker-compose up -d

# Uygulama loglarını görmek için
sudo docker-compose logs -f

# Container'ları durdurmak için
sudo docker-compose down

İlk Temas (First Contact - 29 Mayıs 2024, 21:00 suları)

NOT : Solution içerisindeki Docs klasöründe Thunder Client aracına ait REST test çıktıları yer almaktadır. Bunları VS Code ortamınıza import ederek kullanabilirsiniz.

Sistemde RabbitMq'yu ayağa kaldırdıktan sonra GamersWorld.EventHost uygulamasını başlattım. İlk mesaj gönderimini test etmek için localhost:15672 portundan ulaştığım RabbitMQ client arabirimini kullandım. Burada Queues and Streams kısmında report_events_queue otomatik olarak oluştu. Publish Message kısmında type özelliğine ReportRequestedEvent değerini verip örnek bir Payload hazırladım.

{
  "TraceId": "edd4e07d-2391-47c1-bf6f-96a96c447585",
  "Title": "Popular Game Sales Reports",
  "Expression": "SELECT * FROM Reports WHERE CategoryId=1 ORDER BY Id Desc"
}

Mesajı Publish ettikten sonra host uygulama tarafından otomatik olarak yakalandığını düşen loglardan anlamayı başardım.

First Test

İkinci Temas (30 Mayıs 2024, 22:00 suları)

SystemHome'te SystemMiddleEarth'nin bilgilendirme amaçlı olarak kullanacağı (Rapor hazır, rapor ifadesinde hata var vb durumlar için) GateWayProxy isimli bir Web Api yer alıyor. Bu serviste kendisine gelen mesajlara göre ReportReadyEvent veya InvalidExpressionEvent gibi olayları üretiyor. Dolayısıyla rabbit mq kuyruğunu kullanan bir servis söz konusu. Bu servise aşağıdaki gibi örnek talepler gönderebildim.

Rapor hazır taklidi yapan bir mesaj.

{
  "TraceId": "edd4e07d-2391-47c1-bf6f-96a96c447585",
  "DocumentId": "200-10-edd4e07d-2391-47c1-bf6f-96a96c447585",
  "StatusCode": 200,
  "StatusMessage": "Report is ready and live for 60 minutes",
  "Detail": ""
}

Rapordaki ifadede ihlal var taklidi yapan bir mesaj.

{
  "TraceId": "edd4e07d-2391-47c1-bf6f-96a96c447585",
  "DocumentId": "",
  "StatusCode": 400,
  "StatusMessage": "ExpressionNotValid",
  "Detail": "There is a suspicious activities in expression."
}

Örnek çalışma zamanı görüntüsü ise şöyle oluştu...

Second Test

Redis Stream Entgrasyonu (9 Haziran 2024)

System MiddleEarth içerisinde yer alan Kahin.ReportingGateway, gelen bir rapor talebini aldıktan sonra raporun hazırlanması için bir süreç başlatır. Bu süreç muhtemelen uzun sürebilecek bir iştir. Bu nedenle System MiddleEarth içinde bir mesajlaşma kuyruğu söz konusudur. Bu sefer RabbitMQ yerine Redis kullanılmıştır. Redis tarafı in-memory de çalışabilen dağıtık bir key:value store olarak düşünülür ancak aynı zamanda Pub/Sub modelini de destekler. Dolayısıyla gelen rapor talebini burada kuyruğa bırakıp bir dinleyicinin almasını ve işlemleri ilerletmesini sağlayabiliriz. Redis tarafı yine docker-compose içerisinde konuşlandırılmıştır. Sistemde bir docker imajı olarak ayağa kalkar. Web uygulamasından geçerli bir rapor talebi Kahin.ReportingGateway'e ulaştığında redis'e düşen mesaj kabaca aşağıdaki komutlar ile yakalanabilir.

# Önce redis client terminale girilir
sudo docker exec -it distributedchallenge-redis-1 redis-cli

# Key'ler sorgulanır
keys *

# Key içeriklerine bakılır
XRANGE reportStream - +

Bir deneme sonrası sistemde oluşan görüntü aşağıdaki gibidir.

Runtime_06

SonarQube Genişletmesi

Projenin teknik borçlanma değerlerini ölçümlemek ve kod kalitesini iyileştirmek için Sonarqube aracını kullanmaya karar verdim. Local ortamda Sonarqube kurulumu için Docker imajından yararlanılabilir. Öncelikle sistemde aşağıdaki gibi bir kurulum yapılır.

sudo docker run -d --name sonarqube -e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true -p 9000:9000 sonarqube:latest

Kurulum sonrası localhost:9000 adresine gidilir ve standart kullanıcı adı ve şifre ile giriş yapılır (admin,admin) Sistem ilk girişte şifre değişikliği istenebilir. Sonrasında bir proje oluşturulur. Bende DistributedChallengeProject isimli bir proje oluşturdum ve .Net Core taraması yapması için gerekli adımları işlettim. SQ, proje için bir key üretecektir. Bu key değerinden yararlanılarak tarama aşağıdaki terminal komutları ile başlatılabilir. Zaten anahtar değer üretimi sonrası SQ hangi komutları çalıştıracağınızı dokümanda gösterir.

# Sistem yüklü olması gereken araçlardan birisi
dotnet tool install --global dotnet-sonarscanner

# Tarama başlatma komutu
dotnet sonarscanner begin /k:"DistirbutedChallengeRadar" /d:sonar.host.url="http://localhost:9000"  /d:sonar.token="sqp_6be82d1ead44e1675b09dc6f39456909a6f48ad8"

# Solution için build operasyonu
dotnet build

# Tarama durdurma komutu
dotnet sonarscanner end /d:sonar.token="sqp_6be82d1ead44e1675b09dc6f39456909a6f48ad8"

İlk tarama sonuçlarına göre projenin şu anki skor kartı aşağıdaki ekran görüntüsündeki gibidir.

Sonar Scanner Day 1

Tarama yaklaşık 1200 satır kod tespiti yapmış. Bunun %3.1'inin tekrarlı kodlardan oluştuğu ifade ediliyor (Duplicate Codes) Kodun güvenilirliği ile ilgili olarak 21 sorunlu nokta mevcut. Ayrıca bakımı maliyeti çıkartacak derecede sıkıntılı 41 maddemiz bulunuyor. Bunlardan birisinin etkisi yüksek riskli olarak işaretlenmiş. Ne yazık ki şu an itibariyle Code Coverage değerimiz %0.0. Dolayısıyla birim testler ile projenin kod kalitesini güçlendirmemiz lazım. Dolayısıyla proje kodlarımız henüz birkaç günlük yol katetmiş olmasına rağmen teknik borç bırakma eğilimi gösteriyor.

Secure Vault Entegrasyonu

Solution içerisinde yer alan birçok parametre genelde appsettings konfigurasyonlarından besleniyor. Burada URL, username, password gibi birçok hassas bilgi yer alabilir. Bu bilgileri daha güvenli bir ortamda tutmak tercih edilen bir yöntemdir. Hatta secret bilgileri dev,test,pre-prod ve prod gibi farklı dağıtım ortamları için farklılaştırılabilir. Cloud provider'larda bu amaçla kullanılabilecek birçok Vault ürünü söz konusu. Bunlardan birisi ve ilk denediğim Hashicorp'un Vault ürünü idi. Ancak bir sebepten VaultSharp nuget aracından eklenen key değerlerini çekmeyi başaramadım. Bunun üzerine alternatif bir yaklaşım aradım ve LocalStack'te karar kıldım. Bu ortam development testleri için yeterli. Localstack kısaca bir Cloud Service Emulator olarak tanımlanıyor. Örneğin AWS Cloud Provider'a bağlanmadan local ortamda AWS'nin birçok özelliğini kullanabiliyoruz. Ben AWS'nin Secrets Manager hizmetini local ortamda kullanmaktayım (Bir SaaS - Software as a Service çözümü). İlk denemeyi System Middle Earth'teki Kahin.ReportingGateway üzerinde yaptım. Bu uygulama Redis ve SystemHAL'deki EvalApi adres bilgilerini normal şartlarda appsettings dosyasında okuyor. Bunların vault üstünden karşılanması için gerekli değişiklikler yapıldı.

# LocalStack docker-compose ile ayağa kalktıktan sonra aws komut satırı aracı ile yönetilebilir
# Ubuntu tarafında bu kurulum için şu adımlar izlenebilir
sudo apt update
# yoksa python3 yüklenebilir
sudo apt install python3 python3-pip -y 
pip3 install awscli --upgrade --user
echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
aws --version

# Örnek secret key:value çiflerinin eklenmesi için
aws configure set aws_access_key_id test
aws configure set aws_secret_access_key test
aws configure set region eu-west-1

aws --endpoint-url=http://localhost:4566 secretsmanager create-secret --name RedisConnectionString --secret-string "localhost:6379"
aws --endpoint-url=http://localhost:4566 secretsmanager create-secret --name EvalServiceApiAddress --secret-string "localhost:5147/api"
aws --endpoint-url=http://localhost:4566 secretsmanager create-secret --name HomeGatewayApiAddress --secret-string "localhost:5102"

# Secret bilgilerini görmek için (tümü)
aws --endpoint-url=http://localhost:4566 secretsmanager list-secrets

# Belirli id değerine sahip olanların değerlerini görmek için
aws --endpoint-url=http://localhost:4566 secretsmanager get-secret-value --secret-id RedisConnectionString
aws --endpoint-url=http://localhost:4566 secretsmanager get-secret-value --secret-id EvalServiceApiAddress

# Bellir bir secret içeriğini silmek için
aws --endpoint-url=http://localhost:4566 secretsmanager delete-secret --secret-id RedisConnectionString

Vault Runtime

Vault bilgilerini okumak ve her ihtimale karşı docker container sıfırlanırsa yeniden oluşturmak için iki hazır shell script dosyası yer alıyor. add_secrets.sh ile secret'lerin yüklenmesi, get_secrets.sh ile de eklenmiş secret'lerin görülmesi sağlanabilir. Bu shell script'lerinin çalıştırılabilmesi için aşağıdaki komut ile gerekli yetkilerin verilmesi gerekebilir.

chmod +x manage_secrets.sh

Local NuGet Entegrasyonu

Solution içerisinde yer alan birçok proje Secret Vault kullanmak durumunda. Hatta farklı amaçlarla da ortak kütüphaneler kullanmaları gerekebilir. Bazı kütüphaneleri Solution bağımsız olarak düşünüp yerel bir Nuget yönetimi ile ortak kullanıma açabiliriz. Şirket içerisindeki projeler için ortak kullanılabilecek bir çok paket için iyi bir çözüm yöntemidir. Bu amaçla kendi Ubuntu sistemimde yerel bir Nuget server kullanmak istedim. Baguette kullanılabilecek iyi çözümlerden birisi. Web API tarzı platformu sebebiyle nuget paketlerini basit terminal komutları ile eklemek mümkün. Tabii öncesinde bir Nuget paketi hazırlamak lazım. Bunun için yeni bir sistemimiz var. Ortak kullanılabilecek ve DistributedChallenge solution'ına dahil edilmemesi gereken (ki o zaman Add Project Reference olarak kullanarak bir sıkı bağlılık oluştururuz) paketler için SystemSurgent isimli bir klasörümüz var. Nuget paketi haline gelecek kütüphaneleri burada toplayabiliriz. SecretsAgent isimli paketimiz şimdilik AWS Secrets Manager taklidi yapan bir component desteği sağlıyor. Library olarak oluşturduktan sonra aşağıdaki komut ile nuget paketi hazırlanabilir.

# Bir dotnet kütüphanesi için nuget paketi oluşturmak
dotnet pack -c Release

Bu komutla oluşan .nupkg uzantılı dosya Nuget paketimizdir. Bu ve diğer olası paketleri SystemSurgent altındaki Packages klasöründe depolayabiliriz.

Baguette'yi kolaylık olması açısından docker-compose dosyasında konuşlandırdık ve bir container olarak işletiyoruz. Bu uygulama ayağa kalktığında örneğin aşağıdaki komut ile Packages klasöründeki nupkg uzantılı paketleri BaGet veritabanına kayıt edebiliriz.

dotnet nuget push -s http://localhost:5000/v3/index.json *.nupkg

Nuget Server 01

Nuget Server 02

Pek tabii bu yeterli değil. Makinede local nuget store olarak 5000 adresinden hizmet veren Feed bilgisinin de kayıt edilmesi lazım. Bunun için glogal NuGet.config dosyasının değiştirilmesi gerekiyor. Kendi sistemimde ~/.nuget/NuGet adresinde yer alan NuGet.config içeriğini aşağıdaki gibi güncelleyebiliriz.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
    <add key="LocalPackages" value="http://localhost:5000/v3/index.json"/>
  </packageSources>
</configuration>

Bu işlemin ardından esas itibariyle makinedeki tüm .net projelerinde Local Nuget deposundaki paketlerimizi kullanabiliriz.

# Örneğin Kahin.MQ projesinde artık aşağıdaki komut ile SecretsAgent paketimizi kullanabiliriz.
dotnet add package SecretsAgent

Nuget Server 03

Github Actions ve Local Nuget Repo Problemi

Şu anda github actions ile ilgili bir sorun var. Doğal olarak local nuget reposuna github actions'ın erişimi yok. Bunu aşmak için ngrok ürününden yararlandım. Ngrok'a kayıt olduktan sonra şu adresteki talimatlar ile ilerlenebilir. Local repoyu Ngrok'a kayıt etmek için örneğin aşağıdaki gibi bir kullanım yeterli olacaktır.

# local ortama Ngrok client aracını kurmak için
snap install ngrok

# Bize verilen authentication-key ile doğrulanmak için
ngrok config add-authtoken [AuthenticationKey]

# Ngrok tarafından tahsis edilecek domain ile local adrese gelinmesi için
ngrok http http://localhost:5000

Bu komut ile local makineye gelen talepler de izlenebilir.

Ngrok 01

Tabi bir problemim var. ngrok'u her çalıştırdığımızda bizim için vereceği adres bilgisi değişecek. Buna göre dotnet.yml isimli workflow'un içeriğini de güncellemek gerekecek. Daha iyi bir çözüm bulana kadar böyle ilerleyeceğim. Örnek build akışımıza ait yml dosyası da şimdilik aşağıdaki gibi.

name: .NET

on:
  push:
    branches: [ "main","dev" ]
  pull_request:
    branches: [ "main","dev" ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: 8.0.x
      
    # This ngrok address will change every run probably!
    - name: Restore dependencies
      run: dotnet restore --source https://b8d5-78-183-110-17.ngrok-free.app/v3/index.json --source https://api.nuget.org/v3/index.json

    - name: Build
      run: dotnet build --no-restore
      
    - name: Test
      run: dotnet test --no-build --verbosity normal

Bazı Düşünceler (Some Thoughts)

  • Senaryoda farklı sistemler olduğunu düşünmeliyiz. SystemMiddleEarth raporlama tarafını üstleniyor. Gelen rapor ifadesini anlamlı bir betiğe dönüştürmek, işletmek, pdf gibi çıktısını hazırlamak ve hazır olduğuna dair SystemHome' ü bilgilendirmek görevleri arasında. Kendi içerisindeki süreçlerin yönetiminde de Event bazlı bir yaklaşıma gidebilir. Söz gelimi ifadenin bir Gen AI ile anlamlı hale dönüştürülmesi birkaç saniye sürebilecek bir iş olabilir. Dönüştürme işi başarısız ise bununla ilgili olarak da SystemHome'ü bilgilendirmesi gerekebilir. Dolayısıyla bu da yeni bir olayın üretilmesi, SystemHome'e aktarılması ve SystemHome tarafında bu hatanın ele alınmasını gerektirecektir (Çizelgede e1 ile ifade edilen kısım) Tüm çözümü zorlaştırmamak adına belki bu kısım şimdilik atlanabilir.
  • Expression Interpretter : Rapor talebi yapılan ekranda girilen isteğin anlaşılarak bir SQL ifadesine dönüştürülmesinde Gen AI araçlarına ait bir API'den yararlanabiliriz. Örneğin metin kutusuna "Son bir yılda yapılan oyun satışlarından, en olumlu yorum sayısına sahip ilk 50sini getir" dediğimizde Gen AI API'si bunu anlayıp raporlama tarafından çalıştırılması istenen SQL ifadesini veya farklı bir script ifadeyi hazırlayıp Event mesajına bilgi olarak bırakabilir.
  • İsimlendirmeler konusu da önemli. Event olarak ifade ettiğimiz nesneler esasında process'lerde oluşturulup mesaj kuyruğuna bırakılan POCO'lar (Plain Old CLR Objects) Bunları kullanan business nesnelerimiz de var. Yani bir olayla ilgili aksiyon alan (bir eylem icra eden) sınıflar. Bunlar ortak sözleşmeleri (interfaces) uygulamak durumundalar ki Dependency Injection Container çalışma zamanlarınca çalıştırılabilsinler. Tüm bunlarda proje, nesne, metot, değişken isimlendirmeleri kod okunurluğu ve başka programcıların kodu anlaması, neyi nereye koymaları gerektiğini kolayca bulması açısından mühim bir mesele.
  • gRPC Taşımaları : Sistem içerisinde koşan servislerden bazılarını REST tabanlı tasarlamak yerine gRPC gibi de tasarlayabiliriz. SystemMiddleEarth ile SystemHome'ün aralarında Internet olduğunu düşünürsek buradaki haberleşme kanalları pekala REST Api'ler ile tesis edilebilir.
  • Resilience Durumları : Her iki sistemde de ağ üzerinden HTTP protokolleri ile erişilen servisler söz konusu. Bu servislere erişilememe, beklenen sürede cevap alamama gibi durumlar oluşabilir. Dağıtık mimarilerin doğası gereği bunlar olası. Dolayısıyla Resilience stratejilerini de işin içerisine katmak gerekebilir. Bu sürecin ilerki aşamalarında değerlendirebileceğim bir mevzu.
  • Performans İyileştirmeleri : Benzer raporlar şirket kademesindeki farklı personeller tarafından talep edilebilir. Raporun geçerliliğine göre kabul edilebilir bir zaman dilim boyunca rapor taleplerinin SystemMiddleEarth tarafında cache'lenerek saklanması düşünülebilir. SystemHome tarafından yapılan bir rapor talebi bilindiği üzere Kahin sistemine ulaştığında 3ncü parti bir servis sağlayıcı API'si kullanılarak geçerli bir sorgu ifadesine de dönüştürülüyor. Bu kısımda yapılan Evaluate işlemini aynı türde talepler için bir cache mekanizması ile pekala destekleyebiliriz. Bu Gen AI bazlı yorumlayıcının gereksiz yere çağrılmasının da önüne geçer ve hem kaynak tüketimi hem de hızlı reaksiyon verilmesi babında işleri iyileştirir.