HTTP temelli ağ protokolü üzerinden veri alışverişi yapan veya sunucu taraflı operasyonları tetikleyebilen istemci uygulamalarımız için (SPA, Web App, Mobile, IoT) geliştirdiğimiz servislerin (HTTP, REST, APIs) ölçeklendirilmesi ve performansı büyük önem taşımaktadır.

Bu durumda performans için önbelleğe alma (caching), sıkıştırma (compression), indeksleme, akış (streaming), sayfalama (pagination) vb. anahtar kelimeler uygulanabildiği gibi ölçeklendirme için dağıtık mimariler ve burada derinlemesine inceleyeceğimiz Proxy kalıbı ön plana çıkmaktadır.

Özellikle ölçeklendirmeyi uygulama seviyesinde nasıl yapabiliriz bunu inceleyelim. Yatay veya dikey genişleme, daha fazla işlemci (CPU) veya bellek (Memory) temin etme gibi hususları sistemci dostlarımız düşünsün :) önce biz üzerimize düşeni yapalım.

Bir web uygulaması hayal edin;

  1. Her geçen gün kullanıcı sayısı artan ve yoğun isteklere cevap vermek zorunda olan,

  2. İçerisi bolca grid - veri tablosu, filtreleme, otomatik tamamlama vb. bileşenler ile donatılmış,

  3. Bu bileşenler ve birçok sayfa ile JSON formatında veri modellerine ihtiyaç duyan,

  4. Modelleri (veri tiplerini) muhafaza (persistency) etmesi, kalıcılaştırması ve ilişkilendirmesi gereken.

İstediğiniz gibi tasarlamakta özgürsünüz, peki nasıl daha fazla istek cevaplayabilecek şekilde tasarlardınız!? Bu sorunun birden fazla cevabı ve unsuru mevcut. İşte tam bu noktada katkı sağlayabilecek bir yapı (Pattern, Kalıb) üzerinde duracağım.

HTTP Proxy

.NET ve .NET Core için HTTP istekleri yönetebilen HttpClient isminde özel bir tip bulunmaktadır. Desktop, Web, Mobil hatta IoT cihazlar (Internet of Things) üzerinde koşan istemci uygulamalarımız HttpClient sınıfı sayesinde belirlediğimiz uç noktalara asenkron (Task, Async) isteklerde bulunup cevapları işleyebilmektedirler.

Örneğin:

http://localhost:8080/api/lookup/getlist?id=1&tip=2&query=3...

şeklinde uzayıp gidebilen veya değişebilen bir HTTP adresine HttpClient sınıfı ile istekte bulunabilmek için:

yukarıdaki şekilde bir HttpCall metodu yazabilirsiniz. Bu metodu yazmak ilk başta gayet basit görünmektedir ancak uygulamanız büyüdükçe ve HTTP istekleri farklılaşıp çoğaldıkça işler zorlaşmaktadır.

Tüm bu istekleri yönetmek, dönüş tiplerini ayarlamak (Serialization - Deserialization), değişiklikleri özellikle derleme zamanında (Compile Time) görememek ve bana göre en önemli iki eksiğin Unit-Test ve Arayüz (Interface ve Bağımlılıkların Yönetimi [Dependency Injection]) yapısını kullanamamak zaman ve iş gücü kaybına sebebiyet vermektedir.

Burada özellikle büyük ve ölçeklenebilir uygulamalar için daha etkin bir yöntem üzerinden ilerleyeceğiz. Github üzerinde cross-platform ve açık kaynak olarak geliştirdiğim, kodlarına ve örnek kullanımına NetCoreStack.Proxy adresinden erişebileceğiniz bu kütüphane ile;

  1. Uygulama ölçeklendirme,
  2. Type-Safe dağıtık mimari yönetimi,
  3. Performans ve yönetilebilirlik

konu başlıklarını değerlendirmeye çalışacağım.

Proxy tasarım deseni için ASP.NET Core üzerinden bir örnek vermek istiyorum. Örneğin aşağıda ki şekilde tanımladığınız bir arayüzünüz (Interface) var.

Çalışma zamanında bu arayüzden üretilen bir örnek (instance) ile API - Servis metotlarını bu tipi çevreleyebilen bir proxy - vekil aracılığıyla çağırmak istiyoruz. Proxy sayesinde başka bir sunucuda HTTP üzerinden çalışan bir uç noktaya (Endpoint) istekte bulunup operasyonu gerçekleştireğiz. Metot tanımı, aldığı parametreler ve dönüş tipi haricinde bilmemiz gereken hiçbir şey yok!

Bu durumda bize bu arayüzü çevreleyen ve çalışma zamanında IApiContract tipini uygulayan örneği (instance) sağlayan bir yapıya ihtiyacımız var. Bunun için:

metodunu yazıyoruz. Yapıyı biraz daha ileriye taşıyıp ASP.NET Core Startup.cs dosyasına istemci tarafında şu şekilde genişletilmiş bir metot ekleyip taşınabilir bir yapı elde etmiş oluyoruz.

 // Add NetCoreProxy Dependencies and Configuration
 services.AddNetCoreProxy(Configuration, options =>
 {
 	options.Register<IGuidelineApi>();
 });

Burada yine ASP.NET Core' un DI-Container özelliğini kullanarak çalışma zamanında IGuidelineApi arayüzünü şu iki şekilde talep edebilmekteyiz:

private readonly IGuidelineApi _api;
public TestController(IGuidelineApi api)
{
	_api = api;
}

veya

var guidelineApi = HttpContext.RequestServices.GetService<IGuidelineApi>();

böylece uygun Proxy tipine ait örneği elde etmiş oluruz.

Arayüz tipinden görüleceği üzere kullandığı Attribute:

[ApiRoute("api/[controller]", regionKey: "Main")] 

sayesinde bu Proxy - vekil tipin hangi bölgeye (Main) ait olduğunu ve uç nokta (/api/[controller] - token /api/guideline şeklinde yorumlanmalıdır.) yolunun bilgisini içermektedir. Bu noktada; sözleşme (Contract) için uygun ayarları istemci üzerinde belirlediğimiz appsettings.json isimli dosyada şu şekilde netleştiriyoruz:

Böylece istemci uygulamasından IGuidelineApi arayüzünde tanımlı PrimitiveReturn isimli metot için proxy çağrısında aşağıdaki uç nokta yolu belirlenecek,

URL=http://localhost:8080/api/guideline/primitivereturn

devam eden ikinci istekte sırasıyla (RoundRobinManager - farklı uç noktaya)

URL=http://localhost:8282/api/guideline/primitivereturn

istekte bulunacaktır. Proxy ayarları bölümünde RegionKeys isimli tanım ve değerleri bizlere istemci uygulamamızdan kaç farklı uç noktaya erişebileceğimizi göstermektedir. İşte tam bu noktada ölçeklendirmeye ihtiyaç duyduğumuzda başka bir uç noktayı (Contract - Sözleşmenin uyarlandığı bir HTTP uygulamasını işaret eden) basitçe bölgeye eklememiz yeterli olmaktadır!

Birazdan değineceğimiz Proxy çağrı yöntemi ilgili parametreler ve HTTP istek tipi (Verb - POST,GET vb.) şekline göre uç noktaya uygun çağrıyı yapacak formda olacaktır. Bunun için:

İstemci uygulamasında yukarıdaki şekilde DI-Container özelliğini kullanarak ihtiyacımız olan API - Contract - Servis adı her neyse :) örneği (instance) elde edilerek Proxy aracılığıyla HTTP GET isteği yapılacaktır. Basit bir soyutlama ile sade bir arayüzden çalışıyor gibi hissetmiyor muyuz?

HTTP isteklerin cevaplanmasında veri modelleri ve iş katmanı operasyonlarının tamamını ayrı bir uygulama olarak tasarlayabilirsiniz. Böylece Proxy kalıbı ve yardımcı kütüphaneler aracılığıyla dengeli, güçlü ve basit ölçeklenebilir sunucu taraflı - backend uygulamalar yazabilirsiniz.

Github - NetCoreStack.Proxy

Faydalı olması dileğiyle...