Erhan Ballıeker

Resilient Network Services Bölüm 6 – Polly – 4 (Policy Execution, Configuring, Bulkhead, Cache, Policy Wrap)

Selamlar,

Bir önceki yazımda polly nin Circuit breaker ve fallback özelliklerini incelemiştik. .NetFoundation ailesinden olan bu güçlü kütüphanenin birkaç özelliğinden daha bahsedip örnekler gösterip tüm bu seri dahilinde bahsetmiş olduğumuz 5 kütüphanenin tek bir proje birlikte nasıl kullanabileceğine dair örneklere bakacağız.

Policy Execution

Birçok policy oluşturma şekli gördük Polly ile, bazılarının sonunda Execute metodunu kullanıp neler yapılmasını istediğimizi Polly nin Fluent api yapısı ile nasıl kullandığımızı da gördük. Bu execution işine biraz daha detaylı bakmamız gerekirse, şunlardan bahsedebiliriz.

// Action delegate imizi Policy Execute etoduna doğrudan verebiliriz.
var policy = Policy
              .Handle<SomeExceptionType>()
              .Retry();

policy.Execute(() => DoSomething());

// execute ederken esktra context parametrelerini, aşağıdaki örnekte
// bir Dictionary<string, object> parametresi geçmek gibi,
// Policy patternleri içerisinde yakalayıp ekstra işlemler için kullanabiliriz.
var policy = Policy
    .Handle<SomeExceptionType>()
    .Retry(3, (exception, retryCount, context) =>
    {
        // Execute içerisine verdiğimiz, dictionary burada context adıya elimizde oluyor.
        var methodThatRaisedException = context["methodName"];
		Log(exception, methodThatRaisedException);
    });

policy.Execute(
	() => DoSomething(),
	new Dictionary<string, object>() {{ "methodName", "some method" }}
);

// Geriye bir değer dönen şekilde Execute işlemi yapıp bunu bir variable da
// saklayabiliriz
var policy = Policy
              .Handle<SomeExceptionType>()
              .Retry();

var result = policy.Execute(() => DoSomething());

// Hem geriye değer dönüp hem de kullanacağımız pattern için dışarıdan context
// parametreleri verebiliriz.
var policy = Policy
    .Handle<SomeExceptionType>()
    .Retry(3, (exception, retryCount, context) =>
    {
        object methodThatRaisedException = context["methodName"];
        Log(exception, methodThatRaisedException)
    });

var result = policy.Execute(
    () => DoSomething(),
    new Dictionary<string, object>() {{ "methodName", "some method" }}
);

// Daha önce de gördüğümüz gibi, Polly nin FluentApi ile birçok şeyi birarada
// yapabiliriz. Aşağıda ki örnekte;
// * Hem Sql Exception veya Argument Exception yakalama (Fault-Handling)
// * Retry pattern kullanımı
// * en sonra execute yapma şeklini görmüş oluyoruz.
Policy
  .Handle<SqlException>(ex => ex.Number == 1205)
  .Or<ArgumentException>(ex => ex.ParamName == "example")
  .Retry()
  .Execute(() => DoSomething());

Timeout

Optimistic Timeout

Optimistic time-out CancellationToken mekanizması ile çalışıyor. Bu yüzden hem çalışacak olan delegate in buna uygun olması hem de execute işleminin async versiyonunun kullanılması lazım.

// 30 sn içerisinde verilen delegate tamamlanmazsa caller a geri döner.
 
Policy
  .Timeout(30)

// time out u timespan olarak konfigüre etmek istersek.

Policy
  .Timeout(TimeSpan.FromMilliseconds(2500))

// timeout bir Func<TimeSpan> olarak tanımlamak istersek.

Policy
  .Timeout(() => myTimeoutProvider)) // Func<TimeSpan> myTimeoutProvider

// Timeout gerçekleşirse bu timeout işleminden sonra
//ekstra bir delegate kullanmak istersek

Policy
  .Timeout(30, onTimeout: (context, timespan, task) => 
    {
        // ... 
    });

// örnek olarak hangi policy nin timeout a düştüğünü loglamak istersek.

Policy
  .Timeout(30, onTimeout: (context, timespan, task) => 
    {
        logger.Warn($"{context.PolicyKey} at {context.ExecutionKey}: execution timed out after {timespan.TotalSeconds} seconds.");
    });

// Time out işleminden sonra herhangi bir hata varsa bunu yakalayıp loglamak istersek.
Policy
  .Timeout(30, onTimeout: (context, timespan, task) => 
    {
        task.ContinueWith(t => {
            if (t.IsFaulted) logger.Error($"{context.PolicyKey} at {context.ExecutionKey}: execution timed out after {timespan.TotalSeconds} seconds, with: {t.Exception}.");
        });
    });

örnek Execution:

aşağıdaki örnekte, Async olarak çalıştırdığımız policy mizin timeout süresi 30 sn. dikkat ederseniz ExecuteAsync içerisinde yapılan işlem bir CancellationToken alan Http get Request i. ve timeout policy e geçilen CancellationToken ise None. yani ekstra manuel olarak bir cancellation senaryosu istemidiğimiz durum için kullanabiliriz.

Policy timeoutPolicy = Policy.TimeoutAsync(30);
HttpResponseMessage httpResponse = await timeoutPolicy
    .ExecuteAsync(
      async ct => await httpClient.GetAsync(endpoint, ct),        
      CancellationToken.None
      );

yada;

aşağıdaki şekilde dışarıdan cancellation ı destekleyecek şekilde b ir mekanizma kurabiliriz. Bu şekilde ya timeout olunca yada dışarıdan cancel tetiklendiğinde bu policy caller tarafa geri dönecektir.

CancellationTokenSource userCancellationSource = new CancellationTokenSource();

Policy timeoutPolicy = Policy.TimeoutAsync(30, TimeoutStrategy.Optimistic);

HttpResponseMessage httpResponse = await timeoutPolicy
    .ExecuteAsync(
        async ct => await httpClient.GetAsync(requestEndpoint, ct), 
        userCancellationSource.Token
        );

Pessimistic Timeout

Verilen delegate in bir time out kontrolü olmaması ve dışarıdan bir cancellation token kullanılarak manuel bir tetiklenmenin olmayacağı durumlarda bu yaklaşımı kullanabiliriz.


Policy
  .Timeout(30, TimeoutStrategy.Pessimistic)

Örnek execution:

Policy timeoutPolicy = Policy.TimeoutAsync(30, TimeoutStrategy.Pessimistic);
var response = await timeoutPolicy
    .ExecuteAsync(
      async () => await FooNotHonoringCancellationAsync()
      );

Time out gerçekleştiğinde time out policy TimeoutRejectedException exception fırlatır.

Bulkhead

// Execution ları maksimum 12 tane eş zamanlı çalışacak şekilde kısıtlamak için.

Policy
  .Bulkhead(12)

// Execution ları maksimum 12 tane eş zamanlı çalışacak şekilde kısıtlamak için.
// ve bulkhead içerisinde ki tüm slotlar dolu ise 2 tane action ın yer beklemesi için ayarlamak 

Policy
  .Bulkhead(12, 2)

// Eğer execution reject olursa çalışmasını istediğimiz delegate i vermek istersek.
Policy
  .Bulkhead(12, context => 
    {
        // ...
    });

// Bulkhead in durumunu monitor etmek istersek 
var bulkhead = Policy.Bulkhead(12, 2);
// ...
int freeExecutionSlots = bulkhead.BulkheadAvailableCount;
int freeQueueSlots     = bulkhead.QueueAvailableCount;

Bulkhead policy ile luturmuş oduğumuz policy ler hata aldığında BulkheadRejectedException  fırlatır.

Cache

cache-aside pattern in uygulandığı öncelikli olarak kendi cache datasından sonu döndürmeye çalışan bulamadığı durumlarda ilgili delegate i çalıştırmak istersek bu yöntemi kullanabiliriz.

var memoryCache = new MemoryCache(new MemoryCacheOptions());
var memoryCacheProvider = new MemoryCacheProvider(memoryCache);
var cachePolicy = Policy.Cache(memoryCacheProvider, TimeSpan.FromMinutes(5));


var cachePolicy = Policy.Cache(memoryCacheProvider, new AbsoluteTtl(DateTimeOffset.Now.Date.AddDays(1));

var cachePolicy = Policy.Cache(memoryCacheProvider, new SlidingTtl(TimeSpan.FromMinutes(5));

var cachePolicy = Policy.Cache(myCacheProvider, TimeSpan.FromMinutes(5), 
   (context, key, ex) => { 
       logger.Error($"Cache provider, for key {key}, threw exception: {ex}."); // (for example) 
   }
);

TResult result = cachePolicy.Execute(context => getFoo(), new Context("FooKey"));

PolicyWrap

Önceden de söylediğimiz gibi birçok Policy i önceden uygulamamızın ihtiyaç duyacağı senaryolara olarak oluşturup

var policyWrap = Policy
  .Wrap(fallback, cache, retry, breaker, timeout, bulkhead);
policyWrap.Execute(...)


PolicyWrap commonResilience = Policy.Wrap(retry, breaker, timeout);


Avatar avatar = Policy
   .Handle<Whatever>()
   .Fallback<Avatar>(Avatar.Blank)
   .Wrap(commonResilience)
   .Execute(() => { /* get avatar */ });


Reputation reps = Policy
   .Handle<Whatever>()
   .Fallback<Reputation>(Reputation.NotAvailable)
   .Wrap(commonResilience)
   .Execute(() => { /* get reputation */ });  

Bu kadar Polly detayından sonra artık, şimdiye kadar incelediğimiz bu kütüphanelerin bir arada nasıl bir xamarin projemizde kullanabiliriz buna bakacağız.

Bir sonraki yazımda görüşmek üzere.

Resilient Network Services Bölüm 6 – Polly – 3 (Circuit Breaker, Advanced Circuit Breaker, Fallback Policies)

Selamlar,

Bu seri kapsamında en güçlü kütüphane olduğunu düşündüğüm Polly nin detayalrına devam ediyor olacağım. Önceki yazımda Retry,ForeverRetry, Wait and Retry gibi kullanışlı senaryoları detaylı bir şekilde kullanımını kapsayan örneklerine değinmiştim. Bu yazımda Fallback ler ve Circuit Breaker senaryolarını Polly ile nasıl uygulayabileceğimize değinmek istiyorum.

 

Circuit Breaker

Belli bir tipte hataların sıklaşması kendini yinelemesi gibi durumlarda, sistemin sürkli hataları responselar almaya başladığı durumlarda, daha fazla zaman kayına uğratmadan uygulamamızı kısıtlamalar yapabilmemiz mümkün.  Adından da az çok anlaşılacağı üzere bu pattern zaten, bir hatalı request-response lar loop una girilmiş senaryoda müdahil olup, fail-fast-move-on(” yani çok takılma olut bunlar sen işine bak 🙂 “) yaklaşımı ile duruma el atmamızı sağlıyor.

// Belli bir exception tipinden hatayı söylediğimiz sayıdan fazlaca
// aldığımız durumlarda bu loop u kesmek için
// belli bir bekleme süresi verip circuit-break ettiğimiz yani bu loop u 
// kestiğimiz durum
Policy
    .Handle<SomeExceptionType>()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1));

// Belli tipten bir hatayı belli bir sayıda aldığımız da
// circuit breake i uygulayacağımız süreyi söyleyip, circuit in state inin
// değiştiği durumlarda yapmak istediğimiz aksiyonları söylediğimiz durum
Action<Exception, TimeSpan> onBreak = (exception, timespan) => { ... };
Action onReset = () => { ... };
CircuitBreakerPolicy breaker = Policy
    .Handle<SomeExceptionType>()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);

// Belli tipten bir hatayı belli bir sayıda aldığımız da
// circuit breake i uygulayacağımız süreyi söyleyip, circuit in state inin
// değiştiği durumlarda yapmak istediğimiz aksiyonları 
// ilgili Context i de belirtip kullanarak söylediğimiz durum
Action<Exception, TimeSpan, Context> onBreak = (exception, timespan, context) => { ... };
Action<Context> onReset = context => { ... };
CircuitBreakerPolicy breaker = Policy
    .Handle<SomeExceptionType>()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);

// Circuit state ini incelemek istediğimiz durumlarda CircuitState porpertysi
// ile buna ulaşabiliriz. Mesela HEalthMobitoring için vb gibi.

CircuitState state = breaker.CircuitState;

/*
CircuitState.Closed - Normal işlem. Verdiğimiz action lar bu state de iken çalışabilir.

CircuitState.Open - Verdiğimiz bekleme süresinin dolduğu ve circuit in yeniden açıldığı durum. Verdiğimiz action lar bu state de çalışamaz.

CircuitState.HalfOpen - Bekleme süresi dolduğunda yeniden open state geçmeden önceki süreç, bu sırada actionlar çalışabilir ve
state in birsonraki durumda da open mı yoksa closed mu olacaklarına karar verebilirler.

CircuitState.Isolated - Manuel olarak Circuit ın state ini açık tuttuğumuz durum. Action lar bu state de çalışamaz.
*/

// Manuel olarak İlgili circuit ın state ini open tutup çalıştırabiliriz.
breaker.Isolate(); 
// brekaer ı reset edip closed state e manuel olarak alabiliriz. Bu durumda verdiğimiz 
actionların çalışmasının tetiklenmesini sağlamış oluruz
breaker.Reset(); 

Circuit Open State de iken;

  • Verdiğimiz herhangi bir Action delegate çalışmaz
  • BrokenCircuitException tipi ile call direk fail olur ve bu fail içerisinde fail olmasının sebebi olan son hata gösterilir (Inner exception olarak)

Aşağıda ki şekilde Circuit Breaker nasıl çalıştığını ve state ler arasındaki geçişin nasıl olduğuna bakalım.

c4136890-17cb-11e6-9ffb-3777dd102962

Circuit hayatına ilk olarak Closed state ile başlar. Circuit close olduğunda,

  • circuit-breaker verdiğimiz action delegate leri çalıştırır ve bu actionların başarılı sonuçlanmasını ve hata almalarını gözlemler.
  • Eğer alınan hataların sayısı verdiğimiz değere ulaşırsa, circuit-breaker state OPEN duruma geçer, tüm exceptionları throw eder.
  • Open state de iken action delegate ler çalışmazlar. Yukarıda söylediğimiz gibi circuit-breaker BrokenCircuitException ile hata fırlatır ve break olmasının sebebi olan son hata da InnerException olarak verilir.
  • Verilen süre boyunca circuit-breaker OPEN state de bekler. Sonrasında HALF_OPEN state e geçer.
  • Bu durumda iken verilen sıradaki action delegate çalışır. Eğer bu action herhangi bir handle edilmiş exception yakalar ise circuit-breaker ın state direk olarak OPEN a geri döner. Eğer hata almadan çalışırsa state CLOSE a döner.

Buradaki ters çalışma mantığı kafa karıştırmasın. Bir elektrik devresi gibi düşünün. Circuit CLOSED state de iken herşey yolunda işler çalışıyor demektir. OPEN state e geçtiğinde ise iş akışı durmuş hata fırlatılmış ve circuit bekletiliyor demektir.

 

Advanced Circuit Breaker

Polly nin klasik Circuit Breaker ının yetmediği durumlarda AdvancedCircuitBreaker adında daha detaylıca circuit oluşturmanızı sağlayan bir metodu var. Bu metod sayesinde belli bir hata sayısı yerine çalışan action delegate ler içindeki hata oranını, belli bir süre içinde çalışması gereken minimum action sayısını vs vererek, daha dinamik parametrelerle daha ince hazırlanmış bir circuit oluturabilirsiniz.



Policy
    .Handle<SomeExceptionType>()
    .AdvancedCircuitBreaker(
        failureThreshold: 0.5, 
        samplingDuration: TimeSpan.FromSeconds(10),
        minimumThroughput: 8,          
        durationOfBreak: TimeSpan.FromSeconds(30)                 
);

Circuit breker ın kullanıldığı yerleri ve kullanım şekilleri detayları için aşağıdaki yazıları da okumanızı öneririm.

 

Fallback

Bir hata yakalanması işlemi durumunda hata yerine default bir değer dönmek için kullanacağınız yöntem. Örneğin bir ilan resmi yüklemek adına bir request atıldı, hata alındığında bunun yerine default bir imaj basmak senaryosu gibi.

// Gerçek değer alınamadığında default bir değer dönmek için
Policy
   .Handle<Exception>()
   .Fallback<UserAvatar>(UserAvatar.Blank)

// Default değeri dönerken bir func çalıştırmak istediğimiz de kullanacağımız yöntem
Policy
   .Handle<Exception>()
   .Fallback<UserAvatar>(() => UserAvatar.GetRandomAvatar()) // where: public UserAvatar GetRandomAvatar() { ... }

// hem bir değer dönmek hemde hata olduğuna dair bir action çalışıtırmak istediğimiz zaman kullanacağımız yöntem.
Policy
   .Handle<Exception>()
   .Fallback<UserAvatar>(UserAvatar.Blank, onFallback: (exception, context) => 
    {
        // do something
    });

Polly nin Circuit Breaker patterni nin uygulanması ile ilgili bize neler sağladığını ve Fallback senaryolarında yapabileceklerimizi özetle görmüş olduk.

Bir sonraki yazımda son olarak Polly nin Bulkhead, Cache, COnfiguration ve Policy Execution ile ilgili kısımlarına değinip artık tüm bu kütüphaneleri bir xamarin projesinde nasıl kullanırız buna bakmak istiyorum.

Bir sonraki yazımda görüşmek üzere.

Resilient Network Services Bölüm 6 – Polly – 2 (Fault-Response-Handling, Wait, Wait And Retry, Forever Retry Policies)

Selamlar,

Bir önceki yazımda Resilient network services sistemi kurgularken ihtiyacımız olacak belki de en önemli kütüphane olan Polly e teorik olarak bir giriş yapmıştık. Okmadıysanız önce buradan başlamnızı öneririm. Bu yazımızda Polly nin başlıktaki konularını detaylı inceleyip kullanımlarına bakacağız.

Öncelikle Polly tarafında Fault handling kısmına bir bakalım.

Polly Policy si tarafından yakalanmasını istediğiniz hataları tanımlamak

// tek bir hata tipi. Bir HttpREquest exception olduğundan policy nin yakalamasını istiyoruz 
//bu hatayı.
Policy
  .Handle<HttpRequestException>()

// Spesifik bir hata numarasına göre tek bir hata tipi.
// SqlException lardan sadece 1205 spesific koduna sahip yani sadece Deadlock hatalarını
// yakalamak istediğimizi söylüyoruz.
Policy
  .Handle<SqlException>(ex => ex.Number == 1205)

// Birden çok yakalamak istediğimiz exception tipi olduğunda kullanacağımız yöntem

Policy
  .Handle<HttpRequestException>()
  .Or<OperationCanceledException>()

// Yine birden çok hata tipi için ama her bir hatanın da belli spesifik 
// kodlu olanlarını yakalamak istediğimiz zaman kullanacağımız yöntem.
Policy
  .Handle<SqlException>(ex => ex.Number == 1205)
  .Or<ArgumentException>(ex => ex.ParamName == "example")

// Sıradan hataların yada daha çok async-await kullanırken alacağımız Aggregate Exception lar
//hem top level hemde inner exceptionlarını yakalayabileceğimiz durum. Istersek OperationCanceledException
// da dediğimiz gibi, belli bir condition verebiliriz.
Policy
  .HandleInner<HttpRequestException>()
  .OrInner<OperationCanceledException>(ex => ex.CancellationToken != myToken)

Request in sonucunda yakalamak istediğimiz Result tiplerine göre Policy ler ayarlamak istediğimiz yöntem

 

// bir Condition belirterek result ı handle etmek istediğimiz durum. 

Policy
  .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound)

// birden çok result tipini handle etmek istediğimiz durum.

Policy
  .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
  .OrResult(r => r.StatusCode == HttpStatusCode.BadGateway)

// Sadece belli bir response StatusCode lara göre result ı handle etmek istediğimiz durum

Policy
  .HandleResult<HttpStatusCode>(HttpStatusCode.InternalServerError)
  .OrResult(HttpStatusCode.BadGateway)
 
// Hem hataları hem de responselaru birlikte yakalamak ve handle etmek
//istediğimiz durum. İstediğimiz status codeları bir array de toplayıp,
//hem exceptionları, hem de bu response statüsüne sahip responları handle edip,
// otomatik bir şekilde Retry mekanizmasını çalıştırdığımız durum. 

HttpStatusCode[] httpStatusCodesWorthRetrying = {
   HttpStatusCode.RequestTimeout, // 408
   HttpStatusCode.InternalServerError, // 500
   HttpStatusCode.BadGateway, // 502
   HttpStatusCode.ServiceUnavailable, // 503
   HttpStatusCode.GatewayTimeout // 504
}; 
HttpResponseMessage result = await Policy
  .Handle<HttpRequestException>()
  .OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
  .RetryAsync(...)
  .ExecuteAsync( /* some Func<Task<HttpResponseMessage>> */ )

 

Özellikle son örneğe dikkat etmenizi istiyorum. Bu örnek ile Polly nin gücünü ve bize sağladığı faydaları artık anlamaya başlayabiliriz. Uygulamamızın atacağı bi request için,

  • hata aldığında neler olacağını
  • bazı belirlediğimiz status code la dönen responselar için neler olacağını
  • bu gibi durumlar gerçekleştiğinde tekrar request göndermek istediğimizi
  • ve tüm bu işlemlerden biri olduğunda sonuç olarak ne yapmak istediğimizi yazdığımız func ile, ne kadar fazla senaryoyu 5 satır kod ile Polly e söyleyebiliyoruz.

Api ın kullanım şekli Fluent dediğimiz, metodların nokta ile birbiri ardına yazılması şekline kullanılıyor. Bu da yazım esnasında ayrı bir kolaylık sağlıyor.

Policy nin handle ettiği durumlarda neler yapmasını istediğimiz söylemek istediğimiz durum

Retry

// Yalnızca bir kere tekrar dene.
Policy
  .Handle<SomeExceptionType>()
  .Retry()

// Birden çok kez tekrar etmesini istediğimiz durum
Policy
  .Handle<SomeExceptionType>()
  .Retry(3)

// Birden çok tekrar istiyoruz, ve her bir tekrar sonrasında farklı
// bir işlem yapmak istediğimiz durum. Örneğin önyüzde basitçe Tekrar Deneniyor(1.., 2...)
// gibi basit bir notification için.
Policy
    .Handle<SomeExceptionType>()
    .Retry(3, onRetry: (exception, retryCount) =>
    {
        // ...
    });

// yukarıdaki kullanıma artık olarak, her bir yeni deneme sonrası çalışmasını
// istediğimiz metodu yazarken bize Exception ve retryCount un yanında bir de
// Execute içerisinde kullanmış olduğumuz context i dönen metodun kullanım şekli.
Policy
    .Handle<SomeExceptionType>()
    .Retry(3, onRetry: (exception, retryCount, context) =>
    {
        // ... 
    });

Retry Forever (until succeeds)

Request ten başarılı sonuç alana kadar retry etmek istediğimiz durum. Sınırlı senaryo olsa da böyle durum ile karşılaştığımızda background da çalışan bir request için başarlı sonuç alıncaya kadar retry edebiliriz.

// Sürekli tekrar et.

Policy
  .Handle<SomeExceptionType>()
  .RetryForever()

// her bir tekrar sonrası alınan hata detayı ile birlikte ne aksiyon
//almak istediğimizi söyleyebildiğimiz yöntem.
Policy
  .Handle<SomeExceptionType>()
  .RetryForever(onRetry: exception =>
  {
        // ...      
  });

// her bir hata sonrası ne aksiyonu almak istediğimizi hem hata hemde execute ettiğimiz
// context ile beraber bize dönen metodun kullanım şekli..
Policy
  .Handle<SomeExceptionType>()
  .RetryForever(onRetry: (exception, context) =>
  {
        // ...     
  });

Wait and retry

Tekrar etmeler arasında belli bir bekleme süresi koymak istediğimiz durumlarda kullanacağımız yöntem.

// Her bir tekrar sonrası hata yakalama işleminden sonra beklenmesini
// istediğimiz süreleri belirtiyoruz.

Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(new[]
  {
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(2),
    TimeSpan.FromSeconds(3)
  });

// her bir tekrar deneme sonrası beklenmesini istediğimiz süreyi,
// aynı zamanda da yine her bir tekrar deneme sonraı yapmak istediğimiz ekstra bir
// işlem varsa kullanabilceğimiz yöntem.

Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(new[]
  {
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(2),
    TimeSpan.FromSeconds(3)
  }, (exception, timeSpan) => {
    // ...
  }); 

// yukarıdakine ek olarak bize ilgili context i ve yine 
// o deneme öncsi bekleme süresini, hatayı verip, ilgili aksiyonu yazmak istediğimiz yöntem.
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(new[]
  {
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(2),
    TimeSpan.FromSeconds(3)
  }, (exception, timeSpan, context) => {
    // ...
  });

// her bir yeniden deneme sonrası almak istediğimiz aksiyonu yazarken
// bize (exception, timeSpan, retryCount, context) bu 4 bilgiyi de sağlayan yöntem

Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(new[]
  {
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(2),
    TimeSpan.FromSeconds(3)
  }, (exception, timeSpan, retryCount, context) => {
    // ...   
  });

// Her bir bekleme süresini algoritmik bir sürece bağlamak
// istediğimiz zaman kullanacak olduğumuz yöntem.
// Aşağıdaki örnek için
//  2 ^ 1 = 2 sn
//  2 ^ 2 = 4 sn
//  2 ^ 3 = 8 sn
//  2 ^ 4 = 16 sn
//  2 ^ 5 = 32 sn

Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(5, retryAttempt => 
	TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) 
  );

// Hem bekleme süresini dinamik belirleyip, hemde sonrasında 
// yapmak istediklerimiz için bize, timeSpan, exception ve context i dönen metod.
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(
    5, 
    retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), 
    (exception, timeSpan, context) => {
      // ...
    }
  );

// yukarıdakine ek olarak bize birde retryCount u dönen metod.

Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(
    5, 
    retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), 
    (exception, timeSpan, retryCount, context) => {
      // ...
    }
  );

 

Wait and retry forever (until succeeds)

İşlem başarılı olana kadar tekrar ederken herbir tekrar öncesi beklemek istediğimiz ve bu tekrarlar arasında farklı işlemler yapmak istediğimiz durum.

// sürekli bekle ve tekrar et

Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetryForever(retryAttempt => 
	TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
    );

// Sürekli bekle ve tekrar et ve her bir tekrar deneme sonrası yapmak
// istediğimizi söylediğimiz yöntem.
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetryForever(
    retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),    
    (exception, timespan) =>
    {
        //...      
    });

// Sürekli bekle ve tekrar et ve her bir tekrar deneme sonrası yapmak
// istediğimizi söylediğimiz yöntem.
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetryForever(
    retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),    
    (exception, timespan, context) =>
    {
        //...       
    });

Yukarıda sadece Polly nin, Fault ve Response Handling ini ve bunları yaparken Retry-Forever-Retry ve Wait-Retry patternlerinin uygulanış şekillerini gördük.

Bunların dışında daha bahsedeğim özellikleri mevuct polly nin. Sadece bu kadar bile bir sonraki projenizde olmazsa olmaz kütüphanelerinizden biri olduğunu düşünmenize yeteceiğini düşünüyorum 🙂

Bir sonraki yazımda görüşmek üzere.

Resilient Network Services Bölüm 6 – Polly

Selamlar,

Bu seri kapsamında sizlere tanıtmak istediğim son kütüphane Polly. Bu kütüphaneyi tanıdıktan sonra bunların hepsini bir arada nasıl kullanabiliriz kısmı ile ilgili yazacağım.

Polly, Resilient Network Service katmanı oluşturmak için  en önemli işlerden biri olan resilience and transient-fault-handling dediğimiz kavramı projemizde çok kolay bir şekilde kullanmamızı sağlıyor. Serinin ilk yazısında bahsettiğim gibi, yapılan bir api request in cevabı olarak aldığıımız sonucun neticesinde neler yapmalıyız, tekrar denemeli miyiz, yoksa başka bir yöntem mi uygulamalıyız gibi sorularımız için hem yardımcı metodları mevcut hem de kendi istediğimiz senaryolarıda içerisine dahil etmemize olanak sağlıyor.

Polly target olarak .NetStandard 2.0 ile yazıldığı için,

  • .NET Framework 4.6.1
  •  .NET Core 2.0+
  • Mono
  • Xamarin
  • UWP

target li tüm projelerde çalışabiliyor.

.NetFoundation ın bir parçası olan polly, bir yazılımcının api haberleşmesinde, Retry Pattern, Circuit-Breaker Pattern, Timeout Policy, BulkHead Isolation gibi senaryoları destekleyen extension ları ile, zaman kazanılmasını ve sistemin daha sağlıklı yazılmasını sağlıyor.

Bir xamarin projenizdeki Standard yada Portable projenize yada kullanmak istediğiniz diğer tipteki projelerinizin içinde Polly paketini eklemeniz yeterli.

Install-Package Polly

Yukarıda belirttiğim senaryolar detaylı ve kapsamlı işler olsa da, hem polly nin önerdiği hemde seçerken yardımcı olması adına kısa açıklamalarına bir bakalım.

Retry Policy

Bir api haberleşmesi sırasında bir API den size dönecek bazı hatalar geçici olabilir. Server ın anlık yoğunluktan dolayı geçici olarak kısa süreli verdiği hatalar gibi. Bu durumda belli bir bekleme süresi ile, kullanıcıya farkettirmeden arkada tekrar deneme senaryolarını implemente ederek, kullanıcıya hata göstermekten kaçınabiliriz.

Circuit-Breaker Policy

Bazen yine aşırı yoğunluktan  yada bir bug dan dolayı sistemde zamanla meydana gelen yavaşlıklardan ötürü, aldığınız cevaplar uzayabilir. Sistemin gerçek anlamda sıkıntı çektiğini düşündüğünüz durumlarda, kullanıcıya uzun sürelerce loading şeklinde bekletmektense, daha hızlı olarak hata almış gibi işlemi yarıda kesmek, hem uygulamanıza, hem API nin sunucusuna, hem kullanıcıya daha fazla katkı sağlayabilir. Yani bu işlem belli ki şuan sıkıntılı, “ben bu API nin üzerine daha fazla gitmeyeyim, biraz zaman verelim kendini toparlasın” gibi durumlar için düşünebiliriz.

Timeout

Verdiğimiz timeour süresinin sonrasına hiçbir request aşmayacağını garanti eder ve bu sayede istemediğimiz sürekli beklemelerden kullanıcımızı kurtarabiliriz.

Cache

Anlaşılacağı üzere, benzer requstleri atmayıp cache inden geri döndürüyor.

Bulkhead Isolation

Bunu örneklere geçtimizde daha iyi anlayabiliriz ama özetel bu şu senaryo için var. Bir process hata ona bağlı diğer requestlerin-işlemlerin de hata alacağı baştan belli olsa da sistem bunu bilmeyeceği gereksiz request ve işlem yapma isteği hem sunucu hem de client tarafına gereksiz yük bindireceğinden, bu gibi işlemleri belli bir kümede toplayıp bunlara belli kurallar set edebiliyoruz, dolayısı ile sistemin geri kalanından bu gibi durumları izole edip, bir yerde başlayan bir zincirleme hata senaryosunun tüm uygulamayı kitlemesini, zarar vermesini önleyebiliriz.

Fallback

Hatalar oluyor, olucaktır da. Bu gibi durumlarda sistemin hataya düşmeden, ne şekilde bu hataları handle edeceğimizi yazacağımız delegate ler olarak düşünebiliriz.

PolicyWrap

Düzgün bir sistem yazmaya çalışırken, yukarıda bahsettiğim tüm bu senaryoların birleşebileceği durumlara karşılık olarak, Polly nin bize sunduğu bu senaryolara karşılık gelen policy leri bir arada kullanmamıza olanak sağlayan yapı için bunu kullanacağız.

Birçok API ın kullanımında olduğu gibi, Polly de bu özelliklerinin çoğunun kullanımı için size Policy adında bir sınıf vriyor ve bu sınıfı kullanarak ayrı ayrı her bir haberleşme için istediğiniz policyleri kullanmanıza olanak sunuyor. Bir sonraki yazımda tüm bu senaryoların nasıl kullanılıcağına detaylı olarak değineceğim. Şimdilik bu kadar özet bilgi bence yeterli 🙂

Bir sonraki yazımda görüşmek üzere.

Resilient Network Services – Bölüm 5 – Akavache – 2

Selamlar,

Bir önceki yazımda akavache den bahsetmiş, ne olduğundan, ne faydası olduğundan ve bazı özelliklerinden bahsetmiştim. Bu yazımda bunun devamı olarak ne şekilde kullanabileceğimize, yardımcı metodlarına değineceğim.

Tüm Akavache BlobCache leri aşağıdaki temel metodları destekliyor.

/*
 * Store dan dataları almak..
 */

// tek bir data almak için.
IObservable<byte[]> Get(string key);

// dataların bir koleksiyonunu almak için.
IObservable<IDictionary<string, byte[]>> Get(IEnumerable<string> keys);

// eklediğiniz tipten Deserialize edilmiş tek bir datayı almak için.
IObservable<T> GetObject<T>(string key);

// aynı tipteki Deserialize edilmiş dataların bir koleksiyonunu almak için
IObservable<IEnumerable<T>> GetAllObjects<T>();

// verilen key listesine karşılık gelen birden çok Deserialize edilmiş datayı almak için.
IObservable<IDictionary<string, T>> GetObjects<T>(IEnumerable<string> keys);

/*
 * Store a kayıt eklemek.
 */

// Tek bir data eklemek için
IObservable<Unit> Insert(string key, byte[] data, DateTimeOffset? absoluteExpiration = null);

// bir kolejsiyon eklemek için
IObservable<Unit> Insert(IDictionary<string, byte[]> keyValuePairs, DateTimeOffset? absoluteExpiration = null);

// Otomatik olarak Serialize edilecek bir kompleks objenizi saklamak için.
IObservable<Unit> InsertObject<T>(string key, T value, DateTimeOffset? absoluteExpiration = null);

// Aynı tipteki kompleks objelerinizi saklamanız için.
IObservable<Unit> InsertObjects<T>(IDictionary<string, T> keyValuePairs, DateTimeOffset? absoluteExpiration = null);

/*
 * Store data silmek için.
 */

// tek bir data silmek için
IObservable<Unit> Invalidate(string key);

// belli bir liste silmek için
IObservable<Unit> Invalidate(IEnumerable<string> keys);

// Eğer InserObject ile insert ettiğiniz datalar var ise, aynı şekilde InvalideObject metodu ile silmeniz gerekiyor. 
IObservable<Unit> InvalidateObject<T>(string key);

// aynı şekilde InsertObject edilmiş dataların silinmesi için, tek farkı birden çok datayı silmeniz için bu metod
IObservable<Unit> InvalidateObjects<T>(IEnumerable<string> keys);

// Object olarak kayıt edilmiş olup olmamasına bakılmaksızın tüm dataların silinmesi için
IObservable<Unit> InvalidateAll();

// Belli bir T tipindeki dataların silinmesi için.
IObservable<Unit> InvalidateAllObjects<T>();

/*
 * Saklanan datalar ile ilgili metadata bilgilerini almak için
 */

// eklenen tüm keyleri dönen metod.
IObservable<IEnumerable<string>> GetAllKeys();

// bu key ile saklanmış datanın eklenme tarihini dönen metod.
IObservable<DateTimeOffset?> GetCreatedAt(string key);

// T tipindeki bir objenin eklendiği tarihi dönen metod.
IObservable<DateTimeOffset?> GetObjectCreatedAt<T>(string key);

// verilen key lerin oluşturulma tarihlerinin listesini dönen metod.
IObservable<IDictionary<string, DateTimeOffset?>> GetCreatedAt(IEnumerable<string> keys);

/*
 * Utility metodları
 */

// tüm tamamlanmamış operasyonların diske yazıldığından emin olmak için.
IObservable<Unit> Flush();

// tüm expire olmuş key ve value larını db den silmek için.
IObservable<Unit> Vacuum();

Yukarıdaki tüm metodlar tüm BLobCache tipleri için kullanılabilir. Ama bunlardan daha da kolay olarak işleri çözmemizi sağlayan extension metodlar muhtemelen daha çok tercih edicekleriniz arasında olacaklar.

Bu metodlar aşağıdaki gibi.

/*
 * Username / Login Metodları (Sadece ISecureBlobCache için)
 */

// Verilen host için login bilgilerini saklamak.
IObservable<Unit> SaveLogin(string user, string password, string host = "default", DateTimeOffset? absoluteExpiration = null);

// verilen host taki login bilgilerini okumak.
IObservable<LoginInfo> GetLoginAsync(string host = "default");

// verilen host taki bilgileri uçurmak
IObservable<Unit> EraseLogin(string host = "default");

/*
 * url ve imajların download edilip cachelenmesi
 */

// byte arra olarak download etmek
IObservable<byte[]> DownloadUrl(string url,
    IDictionary<string, string> headers = null,
    bool fetchAlways = false,
    DateTimeOffset? absoluteExpiration = null);

// download edilen datayı imaj olarak okumak.
IObservable<IBitmap> LoadImage(string key, float? desiredWidth = null, float? desiredHeight = null);

// Imajı download edip sonrasında imaj olarak geriye dönmek için.
IObservable<IBitmap> LoadImageFromUrl(string url,
    bool fetchAlways = false,
    float? desiredWidth = null,
    float? desiredHeight = null,
    DateTimeOffset? absoluteExpiration = null);

/*
 * Birleşik işlemler
 */

//Cache deki datayı okumaya çalışır, eğer key i bulamaz ise veya okurken hata
//alırsa verdiğini func ı çalıştırır aldığı sonucu aynı key ile cache e atar.
IObservable<T> GetOrFetchObject<T>(string key, Func<Task<T>> fetchFunc, DateTimeOffset? absoluteExpiration = null);

// GetOrFetchObject gibi sadece async değil.
IObservable<T> GetOrCreateObject<T>(string key, Func<T> fetchFunc, DateTimeOffset? absoluteExpiration = null);

//GetOrFetchObject gibi ama fark olarak verilen key i db de bulsa bile, o sonucu
//hemen döner fakat yine de verdiğiniz func ı çalıştırıp cache deki datayı update eder.
IObservable<T> GetAndFetchLatest<T>(this IBlobCache This,
    string key,
    Func<IObservable<T>> fetchFunc,
    Func<DateTimeOffset, bool> fetchPredicate = null,
    DateTimeOffset? absoluteExpiration = null,
    bool shouldInvalidateOnError = false,
    Func<T, bool> cacheValidationPredicate = null)

Bu iki yazımıda okumuş olsanız dahi akavache github reposunu incelemenizi ve Paul Betts in xamarin evolve 2016 da yaptığı konuşmayı izlemenizi öneririm.

Bir sonraki yazımda görüşmek üzere.

 

Resilient Network Services – Bölüm 5 – Akavache

Selamlar,

Bu yazımda sizlere bu seri kapsamındaki en değerli kütüphanelerden biri olan Akavache den bahsedeceğim.

Akavache async çalışan ve key-value şeklinde data saklamanızı sağlayan, SQLite3 kullanan ve dataları persistant(kalıcı) şekilde tutan bir kütüphane. Desktop ve mobil uygulamalar için eğer lokal veritabanı kullanmak durumunuz var ise biçilmiş kaftan diyebilirim.

Günümüz de neredeyse local storage kullanmayan bir uygulama mevcut değil. Eğer saklamanız gereken dataları da file olarak saklamak gibi bir zorunluluğunuz yok ise kullanacağınız çözüm SQLite olacaktır. Bazı firmalar SQLite üzerinde çalışacak kendi katmanlarını zaten yazmış yada farklı yardımcı kütüphaneler ile ilerliyorlar. Fakat SQLite üzerinde sorgu çalıştırmaki, bilmeden çok fazla hata ve eksik yapmanıza müsait bir durum. En basit örneği verecek olursak, sqlite tarafına attığınız bir sorgu eğer transaction içine alınmadı ise o bunu kendi yapmaya çalışacaktır, bu da sizin sorgunuzun performans kaybetmesi demektir.

Peki hemen öncelikle bu kütüphaneyi nerelerde kullanabiliriz buna bakalım.

Aşağıdaki tüm dotnet platformlarında bu kütüphaneyi kullanabilirsiniz:

  • Xamarin.iOS / Xamarin.Mac
  • Xamarin.Android
  • .NET 4.5 Desktop (WPF)
  • Windows Phone 8.1 Universal Apps
  • Windows 10 (Universal Windows Platform)
  • Tizen 4.0

 

Akavache yi kullandıktan sonra, zaten hali hazırda kendileri de SQLite kullanmalarına rağmen uygulama performanslarında artış olduğunu söyleyen çok firma var. Peki herkes SQLite kullanmasına rağme bu fark neden oluşuyor?

Sebebi şu; SQLite ın nasıl kullanılmaması gerektiğini bilen kişiler tarafından geliştirildi bu kütüphane, ve yılların birikimi open-source bir proje olarak karşımıza çıktı. Yani eğer “Bende yıllardır kullanıyorum ve hali hazırda yazdığım çok iyi çalışan bir kütüphanem var.” diyor olsanız bile yine de kıyaslama yapmanızı öneririm.

Akavache hem gizlilik değeri olan dataları hemde komplex objeleri (imaj, api response, json data) kolayca cihazda saklamınızı sağlıyor.

Temelinde bir core bir key-value byte array store olarak yazılmış (Dictionary<string, byte[]> gibi düşünebiliriz.) ve bunun üzerine inşa ettikleri çok yardımcı ve kullanımı kolay extension metodları mevcut.

Kullanımına bir bakalım.

Akavache öncelikle BlobCache  denen özel bir sınıf ile kullanılıyor diyebiliriz.uygulamanın startup tarafında sadece uyulamanın adını set edeceğiniz tek satır kod ile başlamaya hazır oluyorsunuz.

BlobCache.ApplicationName = “MyApp” veya

Akavache.Registrations.Start(“MyApp”) ve artık hazırsınız.

Cİhazda verilerinizi saklamanız için 4 farklı seçenek mevcut akavache kullanırken.

  • BlobCache.LocalMachine – Normal cache lenmiş data. Bu işletim sistemi farklarına göre sizin haberiniz olmadan, işletim sistemi tarafından uçurulmuş olabiliyor. Bunun garantisini vermiyor, veremezde zaten.
  • BlobCache.UserAccount – User bilgileri. Bazı sistemler bu datayı otomatik olarak cloud ortamlarına yedekleyebiliyor.
  • BlobCache.Secure – password, iban vb hassas dataları saklamanız için kullanmanız gereken seçenek bu.
  • BlobCache.InMemory – Adında da anlaşılacağı üzere sadece uygulamanın lifetime ı süresince datayı saklamak için kullanacağınız seçenek de bu olucaktır.

Xamarin.iOSBlobCache.LocalMachine  seçeneği ile saklanmış dataları, diskte yer boşaltmak için silebilir (tabii uygulamanız çalışmıyor ise o an). Ama  UserAccount ve Secure seçenekleri ile sakladığınız datalar iCloud ve iTunes da yedeklenecektir.

Xamarin.Android  de aynı şekilde LocalMachine üzerinde saklanmış dataları yine diski boşaltmak amaçlı uçurabilir.

Windows 10 (UWP) nin yaptığı bir güzellik ise şu; UserAccount ve Secure tarafında saklanmış dataları cloud a atıyor ve tüm yüklü cihazlar ile senkronize ediyor.

Akavache yi async kullanmak için System.Reactive.Linq api si önemli. async-await bu şekilde çalışır oluyor.

using System.Reactive.Linq;    

Akavache.Registrations.Start("AkavacheExperiment")

var myToaster = new Toaster();
await BlobCache.UserAccount.InsertObject("toaster", myToaster);


var toaster = await BlobCache.UserAccount.GetObject<Toaster>("toaster");


Toaster toaster;
BlobCache.UserAccount.GetObject<Toaster>("toaster")
    .Subscribe(x => toaster = x, ex => Console.WriteLine("No Key!"));

Yukarıda görüceğiniz üzere, using olarak System.Reactive.Linq sayesinde aşağıda UserAccount üzerinden GetObject yaparken  await işlemi yapabiliyoruz.

Hepsinden önce ilk olarak yukarıda da belirttiğim gibi uygulama adını register ettikten sonra akavache kullanıma hazır hale geliyor.

Async-Await ile kullanmak istemiyorsak, aşağıda yazdığımı gibi Subscribe metodu ile cache aldığımız data üzerinden işlemimizi yapabiliyoruz.

Xamarin Linker Önlemi

Bazılarınız uygulama boyutunu düşürmek için Xamarin Linker ı kullanmıştır. Proje taranıp kullanılmadığı düşünülen dll ler uçurulabilir.

Akavache.Sqlite3 dll inin xamarin build tooları ile projeden uçurulmasını önlemenin(ki bu çok öenmli =) iki yolu var

1). İlk yönetim aşağıdaki gibi, dll deki type ları referance alıcak bir dosya eklemek projeye.

public static class LinkerPreserve
{
  static LinkerPreserve()
  {
    var persistentName = typeof(SQLitePersistentBlobCache).FullName;
    var encryptedName = typeof(SQLiteEncryptedBlobCache).FullName;
  }
}

2) İkinci yöntem ise, ilk belirttiğim gibi;

Sadece uygulamanın adını Akavahce.Registrations.Start metodu ile vermek.

Akavache.Registrations.Start("ApplicationName")

ShutDown

Akavacheyi kullanırken unutmamanız gereken birşey de uygulamanın shut down olayında BlobCache.ShutDown() metodunu çağırmamız. Hatta .Wait() etmemiz. Bunu yapmaz isek, queue ya alınmış datalarımız, önbellekten uçmadan kalabilir.

Bu kadar Akavache dn bahsettikten sonra bir sonraki yazımızda daha detaylı kullanımından ve extension metodlarından bahsedeceğim.

Görüşmek üzere.

Resilient Network Services – Bölüm 4 – ModernHttpClient

Selamlar,

Bu yazımda sizelere ModernHttpClient kütüphanesinden ve uygulamanıza performans olarak katkılarından bahsedeceğim.

ModernHttpClient kullanması belki de en basit kütüphane olabilir 🙂 ama etkileri fazlaca.

Bir xamarin uygulamanızda HttpClient ile beraber yapacağınız requestleri u kütüphane ile yaptığınızda çok hızlı çalıştığını göreceksiniz. Bunun sebebi ise bu kütüphanenin arkada platform spesifik taraflarda kullandığı native kütüphaneler. Bu kütüphaneler şunlar

Native development yapmış olanlarınız ya bu kütğphaneleri doğrudan kullanmıştır ya da bu kütüphaneleri arkaplanda kullanan başka kütüphaneler kullanmıştır.(AFNetwork gibi)

Bu iki kütüphane hakkında hiçbir fikriniz olmasa bile uygulamanızda bu ikisini kullanıp daha hızlı bir API haberleşmesi için yapmanız gereken sadece şu;

var httpClient = new HttpClient(new NativeMessageHandler());

Xamarin projenizde sadece Portable yada standard projenize ModernHttpClient ı referans ederek bu şekilde kullanıma başlayabilirsiniz.

Kütüphanenin kullanımı kadar kısa ama kendisi kadar faydalı bir yazı olduğunu umarım 🙂

Bir sonraki yazımda görüşmek üzere.

Resilient Network Services Bölüm 3 – Fusillade

Selamlar,

Önceki yazılarımda Resilient Network services kurgusundan ve bu kurguyu yaparken bize çok destek olacak olan kütüphanelerden bahsetmiştim.  En son REFIT ten bahsetmiştik, şimdi sıra geldi Fusillade den bahsetmeye.

Bu da yine mobil ve desktop uygulamalırınızda daha efektif işler yapabilmenizi sağlayan, Volley ve Picasso kütüphanelerinden esinlenerek oluşturulmuş bir C# kütüphanesi. Volley başarılı bir Http haberleşme kütüphanesi android tarafında. Picasso ise Android developer ların olmaz ise olmalardından bir tanesi. Peki bakalım bu Fusillade de bunlardan esinlenerek neler  yapılmış.

Öncelikle hangi ortamlarda bu kütüphaneyi kullanabileceğimizi görelim.

  • Xamarin.Android
  • Xamarin.iOS
  • Xamarin.Mac
  • Windows Desktop apps
  • WinRT / Windows Phone 8.1 apps
  • Windows Phone 8

Bu ortamların hepsinde bu kütüphaneyi kullanmamız mümkün. Kendisi portable bir kütüphane, NetStandard kütüphanesi kullanmaya başlayanlarda rahatlıkla kullanabilirler.

Peki bu kütüphane bize neler sağlar bunlara bir bakalım. Fusillade temelde HttpMessageHandler kümesi diyebiliriz. HttpClient ile yaptığımız requestlerde arka tarafta bu kullanıldığından, Fusillade kullanarak yaptığınız reuestlerin daha iyi test edilmiş senaryolar üzerine kurulu HttpMessageHandler ile çalışmasından dolayı daha verimli ve responsive uygulamalar yazmanızda yardımcı olur. Özetle Fusillade bize 4 temel fayda da bulunuyor.

  • Auto-deduplication of relevant request: Türkçe mealine gelecek olursak bu deduplication özelliğinin bize sağladığı şey şu; örneğin bir mobil uygulamanız var, sosyal medya olsun. Kullanıcıların postlarını aşağıya doğru diziyorsunuz, bir arkadaşınız 3-5 post atmış. Bunları gösterirken yanında profil resmini de göstereceksiniz değil mi? E bu resimde çok büyük olasılıkla uygulamaya gömülü olmayacağına göre network üzerinden istek atıp alacağınız bir resim olucaktır. İşte bu ve benzeri durumlarda, Fusillade birbirine benzer requestler için yalnızca tek bir request atıyor ve dönen result ı tüm instance lar ile paylaşıyor. Bu da bize daha az network trafiği oluşturucağımız için performans artısı olarak geri dönüyor.
  • Request Limiting : Bu özellik bize Fusillade tarafından anlık yapılan istekleri kısıtlama imkanı sağlıyor. Instagram’ı düşünelim, like butonuna sürekli basıp duruyoruz, her seferinde request atmak yerine bu gibi davranışları kısıtlamak istediğimiz durumlarda bu özelliğini kullanabiliriz. Yapılan istekler, Volley default u olarak anlık 4 tane olacak şekilde limitleniyor. Bunun la istediğiniz gibi oynayabilirsiniz tabii ki.
  • Request Prioritization: Bu özellik aslında en çok kullandığım Fusillade özelliklerinden biri. Uygulamanızda bir ekranda bir çok widget olduğunu aynı zamanda bunlar load olurken de uygulama responsive olduğu için kullanıcının aynı zamanda birşeylere basıp kendi birşeyleri tetiklediğini düşünelim. İşte bu gibi durumlarda Fusillade hangi requestleri önceliklendirip hangilerini geri planda bırakabileceğini söyleyebiliyorsunuz.
  • Speculative requests: Bu da yine özellikle mobil uygulamalrda kullanabileceğiniz güzel bir özellik. Örneğin ilan listesi açtınız uygulamanızda, en üstte de 5 adet reklam var, tıklanma olasılığı yüksek ilanlar. İşte Fusillade ye şunu söyleyebiliyorsunuz; 5mb data sınırın var, bu 5 adet reklamın requestlerini speculative olarak yolla, 5mb bulmadığın sürece bunların detaylarını al getir, 3. de 5mb doldu mu, o zaman gerisini getirme. Bu kullanıcıların tıklama olasılıklarını yüksek gördüğünüz senaryolarınızda requestleri önceden belli bir limit verip yollamınıza ve kullanıcı tıkladığında hiç bekletmeden doğruca ilgili cevabı göstermenize yarıyan bir özellik.

Kullanım Şekli

Fusillade yi kullanmaya başlamanın en kolay yolu NetCache  adındaki sınıf. Yukarıda yazdığım birçok özelliği zaten kapsıyor.

public static class NetCache
{
    public static HttpMessageHandler Speculative { get; set; }
 
    public static HttpMessageHandler Background { get; set; }
    public static HttpMessageHandler UserInitiated { get; set; }
}

Bu sınıfı aşağıdaki şekilde HttpClient ile beraber kullanabilirsiniz.Aşağıda ilgili request e atılacak olan isteğin kullanıcı tarafından tetiklendiğini belirtmiş olduk.

var client = new HttpClient(NetCache.UserInitiated);
var response = await client.GetAsync("https://randomuser.me/api/?results=5");
var str = await response.Content.ReadAsStringAsync();

Console.WriteLine(str);

Speculative requestlerin limiti şu şekilde değiştirebiliriz.

NetCache.Speculative.ResetLimit(1048576 * 5/*MB*/);

Offline Desteği

Cache için ben genelde Akavache kullanıyor olsamda, yada primitive tipler için platform spesific taraflardaki SharedPreference ve NSUSerDefaults u kullanıyor olsam da, Fusillade bize responseları cache leme ve offline olarak uygulamayı çalıştırma fırsatı da veriyor. Cache için olan objenizi NetCahce in RequestCache Propertysine set ettiğiniz anda gelen responselar burada cachlenir.

NetCache.RequestCache = new MyCache();

var client = new HttpClient(NetCache.UserInitiated);
await client.GetStringAsync("https://httpbin.org/get");

Daha sonra kullanıcının offline olduğu durumda NetCache ile HttpClient a request in  Offline çalışmasını istediğinizi aşağıdaki gibi söyleyebilirsiniz.

var client = new HttpClient(NetCache.Offline);
await client.GetStringAsync("https://httpbin.org/get");

Tüm kütüphane yi github  repo sundan incelemnizi öneririm.

Bir sonraki yazımda görüşmek üzere.

 

Resilient Network Services Bölüm 2 – REFIT – 2

Selamlar. Bir önceki yazımda Resilient Network Services için kullanacağımız kütüphanelerden olan Refit için incelemelere başlamıştık. Bu yazımızda bunu tamamlayıp bir sonraki yazılarımız da diğer kütüphanelere değineceğiz.

Önceki yazımızı da Refit ile GET ve POST işlemlerine bakmıştık. Farklı durumlar için incelemiş örnekler göstermiştik. Okumadıysanız bu yazıdan önce Refit ile ilgili ilk yazımı okumanızı tavsiye ederim.

Bu yazımızda refit ile request in Header, Authorization, Multpipart upload, response ve error handling kısımlarına değineceğiz.

Static ve Dynamic Header Tanımlamaları

Static bir header ı refit interface imize ister tüm Interface bazında istersek de metod bazında istediğimiz kadar eklemek için, kullanacağımız yöntem Headers attribute u olucaktır. Örnek olarak;

[Headers("Content-Type: application/json")]
public interface IGitHubApi
{
    [Get("/users/{user}")]
    Task<User> GetUser(string user);
    
    [Post("/users/new")]
    Task CreateUser([Body] User user);
}

şeklinde global olarak(yani interface içerisindeki herbir metod için çalışacak şekilde) tanımlayabiliriz. Ya da aynı attribute u sadece istediğimiz metodun üzerine de yerleştirebiliriz.

Bu zaten çok önemli bir durum değil çok fazla da ihtiyacımız olucak değil, ama kullanımını yine de bilmekte fayda var. Daha önemlisi dinamik olarak değişecek olan headerlar için nasıl bir yöntem kullanacağız. Örneğin her bir request için Authorization header ı göndermek istiyorum. Bu header da dinamil bir header olucak tabii ki. Bunun için ise şu şekilde ilerliyoruz.

[Get("/users/{user}")]
Task<User> GetUser(string user, [Header("Authorization")] string authorization);

var user = await GetUser("erhanballieker", "mytoken.....");

Yukarıda göreceğiniz gibi interface içerisinde tanımlamış olduğumuz metodun header olarak göndermek istediğimiz parametresinin başında Header attribute u nu kullanıp parametre olarak header ın adının ne olacağını veriyoruz(örnekte “Auhtorization” dedik).  bu metodu kullanırken de 2. satırda yazmış olduğumuz gibi “mytoken…” olan paraametre request in Headerlarına Authorization header ı olarak ekleniyor olacaktır.

Refit ile request in headerlerı ile oynamakta bu kdar basit hale gelmiş durumda. Son olarak header lar kısmını bitirmeden şundan bahsedelim, Headerları tanımalamak için 3 yer gördük interface üzerinde, metod üzerinde ve parametre üzerinde, peki bunlardan hangisi daha öncelikli. yani aynı header ismi ile üçünede bir değer koysak hangisi kazanır,

öncelik sırası şöyle;

  1. Parametre başına tanımlanan değer.
  2. metod başına tanımlanan değer
  3. interface seviyesinde tanımlanan değer.

yani aşağıdaki gibi bir örnek için request e “MyHeader” ismiyle eklenecek olan değer parametre olarak verilen değerinki olucaktır.

[Headers("MyHeader: MyHeaderInterface")]
public interface IGithubApi
{
   [Headers("MyHeader: MyHeaderMetod")]
   public Task GetUser(string username, [Header("MyHeader")] string parameter = "actaulValue"]);
}

Multipart Upload

[Multipart] attribute u ile işaretlemiş olduğumuz metodlar multipart content type olarak submit olucaklar.desteklenen multipart methods parametre tipleri aşağıdaki gibidir.

  • string (parameter name will be used as name and string value as value)
  • byte array
  • Stream
  • FileInfo

parametrenin adı multipart datanın adı olarak set edilir default olarak. Yine AliasAs kullnarak bu durumu değiştirebiliriz.

public interface ISomeApi
{
    [Multipart]
    [Post("/users/{id}/photo")]
    Task UploadPhoto(int id, [AliasAs("myPhoto")] StreamPart stream);
}

Bu metoda multipart olarak data göndermek için aşağıdaki şekilde kullanabiliriz.

someApiInstance.UploadPhoto(id, new StreamPart(myPhotoStream, "photo.jpg", "image/jpeg"));

Response Handling

Refrofitin aksine Refit te synchronous api call yapamıyoruz. Tüm requestler Task ile veya IObservable ile async olarak yapılmalı.  Generic parametresi verilmemiş bir Task olarak metodu tanımlamak sadece request in başarılı tamamlanıp tamamlanamadığını bize söyler o kadar.

[Post("/users/new")]
Task CreateUser([Body] User user);


await CreateUser(someUser);

Eğer parametre tipi HttpResponseMessage veya string ise content olduğu gibi geriye döner. Task objesi içerisine bir model verirsek dönen response Refit tarafından otomatik olarak istediğimi model e deserialize edilir ve o şekilde bize ulaşır.

//response string olarak geri döner. (ham json data gibi)
[Get("/users/{user}")]
Task<string> GetUser(string user);

//tüm response IObservable olarak Reactive Extensionlar ile kullanıma hazır şekilde döner.
[Get("/users/{user}")]
IObservable<HttpResponseMessage> GetUser(string user);

//response verilen generic tipe dönüştürülüp bize ulaşır.
[Get("/users/{user}")]
Task<User> GetUser(string user);

Genel olarak bir api ile haberleşirken herhangi bir modelin CRUD işlemlerini yapacak endpoint e request atmamız gerekecektir. Bunun için her model için ayrı ayrı interface veya metodlar tanımlamak yerine, Refit bize generic olarak interfaceleri tanımalama imkanı da sunuyor.  Aşağıdaki örnekte ki gibi tüm Refit interface imizi generic bir parametre tipine bağlı olarakta tanımlayabiliriz.

public interface ICrudApi<T, in TKey> where T : class
{
    [Post("")]
    Task<T> Create([Body] T payload);

    [Get("")]
    Task<List<T>> ReadAll();

    [Get("/{key}")]
    Task<T> ReadOne(TKey key);

    [Put("/{key}")]
    Task Update(TKey key, [Body]T payload);

    [Delete("/{key}")]
    Task Delete(TKey key);
}

Yukarıda göreceğiniz gibi projemizde ki herbir modelin crud işlemleri için bu şekilde bir Refit interface i tanımlayabiliriz. Herbir request için ilgili HttpVerb ü verilip, Task içerisinide geri dönen response un deseriazlize edilecek generic type veriliyor.

Son iki yazımızda Refit e oldukça değinmiş incelemiş olduk. Bir sonraki yazımızda Resilient Network Services serimizde Fusillade in kullanımı ile devam ediyor olacağız.

Görüşmek üzere.