.Net Core ile ApiGateway Geliştirimi

Berk Emre Çabuk
7 min readMay 2, 2020

--

Heimdall the Gatekeeper

İskandinav mitolojisinde efsanevi Asgard şehrini bilmeyen yoktur. Bu mükemmel şehre tabi elini kolunu sallayan giremiyor. Şehre girmek için öncelikle Heimdall’ın seni tanıması izinin var mı kontrol etmesi lazım eğer bir sorun yok ise ancak öyle şehre girip istediğin yere gidebilirsin. Tabi Heimdall bunların hepsini not ediyor ki kim ne zaman ne için geldi bilmesi lazım. İşte Asgard için Heimdall’ın önemi ile Microservice dünyasında Api Gateway’in önemi aynı diyebiliriz.

Api Gateway, çoğunlukla microservice yapılarda karşımıza çıkmaktadır. Temel çalışma mantığı servislerimizin her bir uç noktasını dışarıya açmak yerine Api Gateway üzerinden gelen istekler ilgili servislere dağıtılıyor yani aslında reverse proxy işlemi gerçekleştiriyor. Ee bunu Nginx de yapıyor ama Api Gateway’in tek işlevi bu değil aşağıda tüm işlevlerine değinmeye çalışacağım tabi avantajları olduğu gibi dezavatajı da bulunmaktadır. Mesela Api Gateway down olursa veya herhangi bir sebepten ulaşılamaz ise aslında bütün servislerinize erişim gidiyor demektir. Yazının devamında kısaca diğer avantajlarından bahsedip ardından her birini örnek ile açıklamaya çalışacağım.

Routing:

Dışarıdan gelen bütün istekler api gateway tarafından karşılanır. Bu gelen istekler routing map’e göre hangi url’in hangi servise yönlendireleceği belirlenip internaldaki servise istek gönderilmektedir. Yine aynı şekilde response da api gateway üzerinden yönlendirilmektedir. Yani kısacası reverse proxy işlemi gerçekleştirilmektedir.

Load Balancer:

Routing özelliği ile birlikte load balance görevi de görmektedir. Biraz Load Balancer’ın tanımı şeklinde olacak bilmeyen arkadaşlar için, gelen trafiği api gateway üzerinden farklı sunucularda çalışan görevi gerçekleştiricek asıl servislere yönlendirir. Böylelikle yoğun trafik alan bir sistemde yükü eşit veya belli kurallara göre farklı sunuculara dağıtmış olur. Ayrıca servislerin ayakta olup olmadığını belirli aralıklarla kontrol ederek sağlıklı çalışmayan sunuculara trafiği yönlendirmez.

Authentication:

Dışarıdan gelen istekler için yetki kontrolünü Api Gateway üzerinde gerçekleştirebilirsiniz böylelikle internaldaki servislerinizde yetki kontrolü yapmanıza gerek kalmaz. Yetki kontrolü olmadan açmak istediğiniz servisleriniz de olabilir tabi bunları configuration üzerinde zaten belirtebiliyorsunuz örneklere geçtiğimizde detaylı göreceğiz.

Caching:

Api Gateway sayesinde bazı responselarımızı belleğe alabiliriz. Bunun bize sağladığı iki fayda vardır; birincisi gelen belirli requestleri servise gitmeden direk cevap verebiliriz. İkinci faydası ise Circuit Braker Pattern uygulamak istediğimiz zaman yine daha önce bellekte bulundurduğumuz anlamlı hata mesajlarını(veya ne dönmek istiyorsak) Api Gateway üzerinden dönebiliriz.

Kısaca Circuit Braker Pattern’den bahsetmek gerekirse; Microservice bir mimarimiz olduğunu ve bu yapımızda A servisinin B servisini çağırdığını düşünelim. Servisler arası iletişimde bir sorun yaşandığı taktirde(B servisini down olması) gelen requestler servisler arasındaki isteklerin timeout’a düşmesini bekleyecekdir. Bu sorun ise hem requestlere geç cevap verilmesine hem de gereksiz yere a servisine yük oluşturmaya sebep olacaktır. Circuit Braker ise böyle bir durum yaşandığında belirlediğimiz fail threshold değeri aşıldığı taktirde gelen bu benzer requestleri A servisine yönlendirmeden daha önceden hazırlanan mesajları/modelleri dönmemize imkan sağlıyor. Bu süre zarfında A servisinin isteklere cevap verebilecek duruma geldiğini test etmek için ara ara istek göndermektedir eğer düzeldiyse eski haline geri dönüp istekleri tekrardan A servisine yönlendiriyor. Kısaca bahsetmek istediğim bu yöntemi uygulamamızda Api Gateway bize kolaylık sunuyor diyebilirim.

Circuit Braker Pattern Schema

Logging:

Api Gateway üzerinden log atabiliriz hatta requestin header bölümüne RequestId/CorrelationId ekleyerek internal servislerimizde bu isteğe bağlı gerçekleşen işlemlerin attığı logları bu requestId üzerinden takip edebiliriz. Microservice mimarilerde bir istek sonucunda bir çok servis tetiklenip işlemler gerçekleştirebilir böyle bir durumda log takibi zorlaştığından dolayı bu yöntem(requestId/correlationId) gerçekleşen işlemin başından sonuna kadar takip etmeyi kolaylaştırıyor.

Genel bir bilgi verdikten sonra pratik üzerinden her bir özelliğini uygulayıp daha detaylı anlatmaya çalışacağım. Öncelikle bu yazı serisinin devamında api gateway kullanımı üzerine örnekler yaparken farklı teknolojilerde kullanacağız.

Öncelikle Api Gateway için Ocelot kütüphanesini kullanacağım. Ocelot’u tercih etme sebeplerim; .Net tabanlı open source olması ayrıca implementasyonu çok kolay ve dokümantasyonu yeterli seviyede diyebilirim.

Örnek projemizde toplamda 5 servisimiz olacaktır.

ProductService, ProductSecondService ve UserService servislerimiz bizim internal servislerimiz olacaktır ve bunlara gelen istekleri yönlendirecek servisimiz ise ApiGateway olacaktır. Öncelikle ApiGateway servisimize Ocelot kütüphanesini ekleyip konfigürasyonunu yapacağız.

ocelot.json

DownstreamPathTemplate : Gelen request’in ilgili internal servisteki endpoint’inin karşılığı.

DownstreamHostAndPorts : Request’in yönlendirileceği internal servisin host ve port bilgisi belirlenir.

UpstreamPathTemplate : Requestler içerisinde eşleşecek url yapısını belirtmektedir.

UpstreamHttpMethod : Requestler için geçerli olan http metodları belirlenir.

LoadBalancerOptions : Load Balancer ayarları yapılır. Kullanılacak algoritma tipini belirtmek için “Type” bilgisi doldurulur bunun dışında isterseniz kendi custom algoritmanızı geliştirip tanımlayabilirsiniz.

AuthenticationOptions : Authentication ayarları yapılır. Burada kullanılacak olan authentication schema anahtarı belirleniyor yani serviste belirlenen yöntemlerden hangisi kullanacağını belirtmek için tanımlanan key’i “AuthenticationProviderKey” ile bildiriyoruz.

GlobalConfiguration : Api Gateway servisimizin ayarları diyebiliriz. BaseUrl ile Api Gateway’in ayağa kalktığı port ve host bilgisini belirliyoruz. Ayrıca RequestIdKey tanımı yapılarak gönderilen isteklere requestId eklenmiş oluyor tabi bunu istersek route bazında da yapabiliriz.

İlgili tanımlamaları yaptıktan sonra Program.cs de okuyacağı configuration dosyasını tanımlayıp ardından Startup.cs de middleware olarak tanımladıktan sonra teste hazır.

Yaptığımız Konfigürasyonda Product servisi için iki farklı host bilgisi girdik ve gelen isteklerin bu iki servise nasıl yönlendirileceğini belirlemek için LoadBalancerOptions tanımlamasını yaptık. Yani sadece Product servisi için geçerli olacaktır ve algoritma türü olarak RoundRobin tercih ettim.

RoundRobin, gelen istekleri belirlenen sunucular üzerinde sırayla dağıtmaktadır. Bunun dışında sık kullanılan diğer bir algoritma ise Least Connection, istekleri yönlendireceği zaman hangi sunucu daha az bağlantı sayısına sahip ise onu tercih eder.

Test etmek için http://localhost:5100/product-service/product istekte bulunuyorum. product-service/{anyThing} gelen istekler localhost:5002/api/{anyThing} veya localhost:5003/api/{anyThing} e yönlendirilmektedir yani asıl product servislerimize. burada üst üste attığımız iki istekte load balancer sayesinde iki farklı servisten yanıt geldiğini göreceğiz.

Sıra geldi Authentication için basit bir JWT örneğine. Ocelot.json da yaptığımız konfigürasyonda product ve user servisler için AuthenticationOptions eklemiştik yani bu servislere gidecek isteklerimizin token’ı olması gerekmektedir. Bunun bize avantajı diğer servislerde tekrardan yetki kontrolü yapmamıza gerek kalmıyor. Lakin servisler arasındaki iletişimde belli yetki kontrolleri yapmanız gerekiyorsa(belli servisler belli servislere erişebilir vb.) Api Gateway üzerindeki yetkilendirme sizin için yeterli olmayacaktır. Mevcut senaryomuz üzerinden devam etmek gerekirse öncelikle token’ı oluşturacağımız AuthenticationService’den başlayalım.

Öncelikle token’ı hazırlayan AuthenticationService ve kontrol edecek ApiGateway servislerine IdentityServer4.AccessTokenValidation paketini ekliyoruz.

AuthenticationController.cs

Basit bir örnek olması için sadece hardcoded olarak eklediğimiz kullanıcı bilgisi dışında başka bir kullanıcıya token verilmemektedir. appsetting’de bulunan belirlediğimiz key, issuer, audience bilgisini aldıktan sonra imzayı oluşturup token’ı hazırlıyoruz. Token’ın geçerlilik süresini ise 1 gündür.

Api Gateway tarafında ise Startup.cs üzerinde Authentication’ının JWT kullanıldı ve şema key ise “TestKey” olarak belirlenmiştir. Bunun önemi servis ayağa kalkerken ocelot.json’da belirttiğimiz providerKey ile eşleşen şema var mı yok mu kontrol etmektedir. Token kontrolünü yaparken appsettings’den getirilen issuer, audience ve key ile elde edilen imza eşleşmesi üzerinden sağlamaktadır.

Api Gateway projesindeki Startup.cs

Şimdi testimizi postman üzerinden gerçekleştirelim. İlk önce header’a token eklemeden istek attığımızda requestin ilgili servise gidemen Api Gateway üzerinden 401 Unauthorized hatası aldığını göreceğiz.

Ardından ocelot.json üzerinde identity-service endpointine istekte bulunup token’ımızı alıp tekrar deneyeceğiz.

Token’ımızı elde ettikten sonra daha demin 401 aldığımız isteğin header alanına ekleyip tekrar deniyoruz Api Gateway’den 401 almayıp, istediğimiz bilgileri getiriyor.

Yazı tahminimden uzun olduğundan Caching bölümüne ileride yazacağım Circuit Braker Pattern yazımda değineceğim. Bundan dolayı son olarak Logging bölümünü anlatacağım.

Aslında bu bölüm biraz daha bonus gibi olacak. Api Gateway üzerinde loglamanın dışında ELK’nın nimetlerinden faydalanmanın güzelliklerinden de kısaca bahsetmiş olacağım :)

Öncelikle Api Gateway’de attığımız loglar üzerinden bir çok farklı rapor/izleme elde edilebilir(hangi servis ne kadar çok trafik alıyor, servis bazında gelen isteklere geri dönüş süremiz vb.). Bunu dışında yazının başında da belirttiğim gibi requestId ile gerçekleşen işlemin diğer servisler üzerindeki log takibi de kolaylaşmaktadır.

Ben örneğimizde logları Elasticsearch’te toplamayı tercih ettim. Böylelikle hem kibana ile birlikte kendimize chartlar hazırlayabilir hem daha hızlı arama yapabiliriz. Bundan dolayı docker üzerinde elasticsearch ve kibana ayağa kaldırmamız gerektiğinden yml dosyası aşağıdaki gibidir.

Tabiki logları Serilog ile oluşturup ardından Elasticsearche aktaracağım. Serilog tercih etmemin sebebi ihtiyaç duyacağınız her türlü ortamla sink olup loglarını akıtabilir ikincisi ise contexti runtime da belirleme seçeneğini sunması. Bundan dolayı bütün servislerimize “Serilog.AspNetCore” ve “Serilog.Sinks.Elasticsearch” paketlerimizi eklememiz gerekiyor. Ardından bütün projelerimizde Program.cs dosyasında değişiklik yapmamız gerekiyor. Logları toplarken minimum seviye information tipi olarak belirleyip hem console hem de Elasticsearch üzerine yazmaktadır. Indexlemeler ise günlük olacak şekilde ayarladım böylelikle her günün logları ayrı bir documentation olarak toplanacaktır.

Sıra geldi test aşamasına öncelikle docker-compose up ile elasticsearch ve kibana yı ayağa kaldırıyoruz. Ardından servislerimizi ayağa kaldırıyoru tabi burada servislerimizi de docker üzerinde ayağa kaldırabilirdik şuan onu eklemedim ama ilerleyen günlerde projede değiştirebilirim. Belirli bir sayıda istek attıktan sonra toplanan loglar üzerinden kibana’da dashboardlar hazırlayabiliriz.

1.Tablo ApplicationName alanına göre gruplanmış log sayılarını gösterirken 2.tabloda ise requestId’ye göre gruplanmış en çok log’a sahip olanları gösteriyor.

Umarım bilgilendirici bir yazı olmuştur. Github profilimde paylaştığım proje içerisinde testlerinizi yapabilmeniz için postman collectionda bulunmaktadır.

--

--