Erhan Ballıeker

Tüm .Net Client Projelerimizde Ortak Kullanabileceğimiz Bir API Client yazalım Bölüm 2

Selamlar,

Bir önceki yazımda hazırlığını yapmış olduğumuz projemizin asıl kısmına gelelim.

Projemize ApiClient adında bir sınıf ekliyorum ve içeriğini aşağıdaki gibi dolduruyorum. Bir göz gezdirip detaylarına değinelim.


 public class ApiClient
        {
            #region fields

            private static ApiClient shared;
            private static object obj = new object();

            #endregion

            #region properties

            internal static IServiceCollection Services { get; set; }
            internal static IServiceProvider ServiceProvider { get; private set; }
            private static ApiClient Shared
            {
                get
                {
                    if (shared == null)
                    {
                        lock (obj)
                        {
                            if (shared == null)
                            {
                                shared = new ApiClient();
                            }
                        }
                    }

                    return shared;
                }
            }

            #endregion

            #region api spesific properties

            private IRandomUserApi _IRandomUserApi { get => ServiceProvider.GetRequiredService(); }

            //Exposed public static props via ApiClient.Shared 
            public static IRandomUserApi RandomUserApi{ get => Shared._IRandomUserApi; }

            #endregion

            #region ctor

            private ApiClient()
            {
                if (Services == null)
                    Services = new ServiceCollection();

                Init();
            }

            #endregion

            #region internal methods

            private void Init()
            {
                ConfigureServices(Services);
                ServiceProvider = Services.BuildServiceProvider();
            }

            private void ConfigureServices(IServiceCollection services)
            {
                services.AddTransient<ITokenService, TokenService>();

                #region AnonymousApi Configuration

                services.AddRefitClient()
                .ConfigureHttpClient(c =>
                {
                    c.BaseAddress = new Uri("http://wordy.azurewebsites.net/");
                    c.Timeout = TimeSpan.FromSeconds(10);
                })
                .AddTransientHttpErrorPolicy(p => p.RetryAsync())
                .AddHttpMessageHandler(serviceProvider =>
                {
                    var token = serviceProvider.GetRequiredService().GetToken();
                    return new AuthTokenHandler(token);
                });

                #endregion

            }

            #endregion
        }

Yukarıdakilerle alakalı olarak şurayı da okumanızı öneririm.

Burada önceki yazımda projeme eklediğim kütüphaneleri kullanmaya başlıyorum artık.

Yukarıda dönen hikaye şu;

  • Singleton bir ApiClient objem var. Tüm refit interfacelerini birer property üzerinden dışarıya expose ediyorum.
  • ConfigureServices metodunu neredeyse Asp.Net Core daki gibi birebir aynı yapmaya çalıştım. IoC conteiner ı oluşturup, refit interfacelerimi ve polly policy lerimi ilgili enpoint e register ediyor
  • Son olarak aynı şekilde yazmış olduğum DelegatingHandler larıda client ıma ekliyorum ve herşey kullanıma hazır hale geliyor.

Bundan sonra herhangi bir clien projesinde şunu yapabilirim

ApiClient.RandomUserApi.GetUser(“3”);

dediğimde Client projem her ne olursa olsun ister bir core web app ister xamarin ister başka birşey,  httpclient factory üzerinden refit, polly, delegating handler lar da kullanarak güzel yapı kurmuş oluyorum.

Bunları istediğimiz gibi şekillendirip güncelleyip, istediğimiz gibi konfigüre edebiliriz ve hiçbir client projemize dokunmamış oluruz.

Ek olarak burada ITokenService ve TokenService diye bir service yazdım. Bunu da şuna örnek olarak kullanabiliriz. Örneğin kullanıcı mobil uygulama da login olduğunda aldığımız token Xamarin.Essentials s Preference paketi ile saklayarak AuthTokenDelegating handler a parametre olarak verebilmenin örneği olsun diye koydum.

Farkli client projeler de bu ITokenService implemenatasyonlarını ayrı yazarak başka türlü yerlerde saklayıp okuyabiliriz.

Xamarin projesi için bu işimizi görecektir.

   public interface ITokenService
    {
        string GetToken();
    }

Xamarin forms projesi için implementasyon;

    public class TokenService : ITokenService
    {
        public string GetToken()
        {
            if (Preferences.ContainsKey(Constants.AuthToken))
                return Preferences.Get(Constants.AuthToken, string.Empty);
            else
                return string.Empty;
        }
    }

Bunu muhtemelen bu ApiClient projesinde değil de client projelerde register etmek daha doğru olacaktır gerçek hayat senaryolarında.

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

Tüm .Net Client Projelerimizde Ortak Kullanabileceğimiz Bir API Client yazalım.

Selamlar,

Daha önceden bahsetmiştim böyle bir konuya girmek istediğimi.

Yeni gelen HttpClientFactory ile de beraber daha önceden yazmış olduğum Resilient Network Services serisinin daha kısasını ve güncellenmiş halini kütüphanelerin detayların da çok fazla boğumladan yeniden incelemek istiyorum.

Amacımız şu;

Bir ApiClient Standard kütüphanesi oluşturalım. Solution ne kadar Client projemiz var ise Xamarin, Web, Console, UWP, WPF farketmez hepsi bu kütüphane üzerinden network haberleşmelerini yapsın(Http üzerinden)

Bu işlem sırasında da önceki yazılarımızda kullandığımız refit ve polly hatta akavache yi de kullanalım. ModernHttpClient a artık çok gerek yok, çünkü proje özelliklerinden Default mu yoksa platform spesifik sınıflarımı kullanmak istediğimizi zaten belirtebiliyoruz aşağıdaki gibi.Capture.PNG

Burada HttpClient implementation ın Managed mı Default mu yoksa Android mi olduğunu seçebiliyoruz. iOS tarafında da durum aynı şekilde.

Peki çok uzatmadan bu kütüphanemizi yazmaya başlayalım ve Asp.Net Core Web App ve Xamarin projelerimizde de kullanalım bakalım.

Bir adet boş solution açıp içerisine önce bir adet Asp.Net Core Web App ve bir adet de xamarin.forms projeleri ekleyelim.

Daha sonra Add New Project diyerek Bir adet .netstandard class library ekleyelim.

Capture.PNG

Bu kütüphanenin diğer tüm client projeleri tarafından kullanılabileceğine eminiz çünkü .netstandard kütüphanesi ekledik.

Projede kullanmak istediğimiz ve resilient network services kısmında bize yardımcı olacak 3 temel şey var.

  • Polly
  • Refit
  • HttpClientFactory

Tüm bunların kullanımı için öncelikle aşağıdaki paketleri projeye teker teker ekliyoruz.

  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Http.Polly
  • Refit.HttpClientFactory
  • Xamarin.Essentials (xamarin tarafında kullanmak üzere şimdilik çok önemi yok)

Projede kullanmak için bir api yazmaktansa yine open apilardan randomuser  kullanalım.

Bunun için daha önceki yazılarımda çokça detayına girdiğim için burada konuyu uzatmadan hemen refit interface imi oluşturucam. Sonrasında da örnek bir tane delegating Handler oluşturucam

Bunlar aşağıdaki gibi;

Random user api si ile haberleşecek olan Refit Interface i;
Projede Endpoints adında bir klasör açıp içerisine aşağıdaki interface i atıyorum

Burada amaç tüm farklı endpointleri ayrı ayrı interfaceler altında toplayıp hepsini ayrı ayrı konfigüre edebilmek.

 [Headers("Content-Type : application-json")]
    public interface IRandomUserApi
    {
        [Get("/")]
        Task<string> GetUser(string results);
    }

DelegatingHandler ım.

 public class AuthTokenHandler : DelegatingHandler
    {
        private readonly string _token;
        private const string TOKEN = "Authorization";
        public AuthTokenHandler(string token)
        {
            _token = token;
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (!request.Headers.Contains(TOKEN) && string.IsNullOrEmpty(_token))
            {
                return new HttpResponseMessage(HttpStatusCode.BadRequest)
                {
                    Content = new StringContent("Missing auth token.")
                };
            }
            else if (!request.Headers.Contains(TOKEN) && !string.IsNullOrEmpty(_token))
            {
                request.Headers.Add(TOKEN, $"Bearer {_token}");
            }

            var response = await base.SendAsync(request, cancellationToken);

            return response;
        }
    }

Bu iki kod bloğu ile ilgili daha detaylara girmek isterseniz aşağıdaki yazılarımı okuyabilirsiniz.

Projenin temel kısımları hazır oldu. Bundan sonraki yazımda asıl sınıfımız olan ApiClient sınıfını yazıp client projelerimizde kullanacağız.

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

Asp.Net Core HttpClientFactory + Polly

Selamlar,

HttpClientFactory ile ilgili yazılarıma bir yenisini de Polly ile kullanımını inceleyerek devam etmek istedim.

Polly, açıkçası farkında olmasanız bile kullanmak zorunda olduğunuz bir kütüphane diyecek kadar ileri gidebilirim =) eğer ki benzer işlevleri zaten zamanında siz kendiniz farklı kütüphaneler olarak yazmadıysanız.

Polly ile ilgili yazılarıma şuradan ulaşabilirsiniz;

Polly sizelere network haberleşmesi sırasında uygulamanızda olması gereken bir çok pattern i extension metodlar halinde sunan ve çok daha fazlasını yapmanıza olanak tanıyan bir kütüphane.

Polly ile ;

  • Error ve Result Handling
  • Retry, WaitAndRetry, ForeverRetry gibi senarto ve patternleri
  • Circuit Breaker ve Advanced Circuit Breaker gibi senaryo ve patternleri
  • Cache, Bulkhead Isolation gibi

birçok şeyi çok basit şekilde uygulamalarınıza implemente edebiliyorsunuz.

Detaylarına yukarıda ki yazılarımdan ve kendi github reposundan ulaşabilirsiniz.

Asp.Net Core projelerimizde HttpClientFactory ile beraber kullanmak için yapacağımız ilk şey nugetten aşağıdaki paketi indirmek

Microsoft.Extensions.Http.Polly

Daha sonrasında kullanım şekli önceki yöntemler ile çok benzer oluyor.

Uygulamanın startup dosyasında aşağıdaki gibi AddHttpHandler dedikten sonra o Client ile ilgili uygulamak istediğiniz policy leri fluent bir şekilde ekleyebiliyorsunuz.

  public void ConfigureServices(IServiceCollection services)
        {
            services.Configure(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddHttpClient("randomuserclient")
                .AddPolicyHandler(Policy.TimeoutAsync(TimeSpan.FromSeconds(10)))
                .AddTransientHttpErrorPolicy(p => p.RetryAsync(3));

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

Yukarıdaki örneğimizde projemize NamedClient olarak eklemiş olduğumuz bir Client ın Polly yi nasıl kullanabileceğini söylemiş olduk.

Nuget ten indirdiğimiz paket ile beraber bize birçok extension metod geliyor.

Bunlardan sadece iki tanesini yukarıdaki senaryoda kullanmış olduk.

  • Time out policy ekleyerek, gidecek requestlerin en fazla 10 sn bekledikten sonra eğer response alamazsa doğrudan polly tarafından hataya düşürülmesini sağladık
  • Haberleşme sırasında geçici sorunlar olduğunda 3 kere yeniden denenmesini istedik. Anlık internet kayıpları, server anlık olarak response verememesi available olamaması gibi durumlarda request 3 kere yeniden denenecek.(RetryPattern)

Kullanım şekline bakığımızda ise öncekilerden hiçbir farkı yok. NamedClient eklemiş olduğumuz için aşağıdaki şekilde kullandık. Typed veya Basic usage olarak ekleseydik de o şekilde kullanacaktık. Polly nin yazarken ki dikkat etmemiz gereken tek kısmı Startup tarafında Policyleri Clientlarımıza implemente ettiğimiz kısım.

 public class HomeController : Controller
    {
        private readonly IHttpClientFactory_randomUserApi;
        public HomeController(IHttpClientFactory randomUserApi)
        {
            _randomUserApi = randomUserApi;
        }

        public async Task Index()
        {
            var result = await IHttpClientFactory.CreateClient("randomuserclient").GetUser("10");
            return View();
        }

      ..........

Bir önceki yazımda Refit ile HttpClientFactory i nasıl kullanırız bundan bahsetmiştim. Bir de bu üçünü yani Refit + Polly + HttpClientFactory i nasıl kullanırız buna bakalım.

Aşağıda basit bir Refit interface i görüyoruz.

namespace HttpClientFactoryRefit
{
    [Headers("Content-Type : application-json")]
    public interface IRandomUserApi
    {
        [Get("/")]
        Task GetUser(string results);
    }
}

Nugetten aynı projemize ;

Refit.HttpClientFactory 

paketini de indirdikten sonra aşağıdaki şekilde hem refit i AddRefitClient diyerek services a ekleyebilir, hem de sonrasında yukarıda ki gibi Polly Policy lerini aynı Client ın devamında ekleyebiliriz.

  public void ConfigureServices(IServiceCollection services)
        {
            services.Configure(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddRefitClient()
                .ConfigureHttpClient(client =>
                {
                    client.BaseAddress = new Uri("https://randomuser.me/api");
                })
                .AddPolicyHandler(Policy.TimeoutAsync(TimeSpan.FromSeconds(10))) 
                .AddTransientHttpErrorPolicy(p => p.RetryAsync(3)); ;

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

Kullanım şeklinde de Refit ile HttpClientFactory i nasıl kullanıyor isek aynı şekilde kullanmaya devam ediyoruz.

public class HomeController : Controller
    {
        private readonly IRandomUserApi _randomUserApi;
        public HomeController(IRandomUserApi randomUserApi)
        {
            _randomUserApi = randomUserApi;
        }

        public async Task Index()
        {
            var result = await _randomUserApi.GetUser("10");
            return View();
        }
       

          .........

Bu yazımızda da hem Polly ile HttpClienFactory nasıl kullanacağımızı görmüş olduk hem de bir önceki yazımızda Refit ile kullandığımız gibi refit i de işin içine dahil ettik.

Refit + Polly + HttpClienFactory üçlüsü güçlü bir Api haberleşme altyapısı için çok büyük artılar sağlayacaktır projenize.

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

Resilient Network Services Bölüm 7 – Xamarin Projemizde Refit, Polly, Akavache, Fusillade ve ModernHttpClient Birlikte Kullanımı Bölüm 3

Selamlar,

Bu serinin son yazısına nihayet gelmiş olduğumuzu düşünüyorum 🙂 Bu seri boyunca çok faydalı 5 farklı kütüphane ve bunların nasıl kullanılacağını ayrı ayrı görmüştük. En son bir xamarin projemizde (ki xamarin olması şart değil herhangi bir .net platformunda da olabilir.) bunları nasıl kullanırız buna bakmaya başlamıştık.

En son yazımda, oluşturduğumuz ApiRequest sınıfı içerisinde refit, fusillade ve modernhttpclient ı kullanabilir olmuştul. Refit interface imizi oluşturmuştuk. Yani aslında api haberleşmesi yapmamız için ihtiyacımız olan tüm temel kısımları yazmıştık. Şimdi uygulama tarafında kullanılacak olan ve genelde BusinessLayer, Application Services vb isimlerle göreceğimiz bir servis katmanı ekleyelim. Bu katmana da haberleşmeyi uygulama bazında ne şekilde yapacağız buna bakalım.

Buna başlamadan önce, son olarak yardım bir sınıf daha oluşturmak istiyorum. Önyüzden gelen önceliklere göre (Fusillade öncelikleri – yani request in olmasını istediğimiz öncelikleri) arka planda ApiRequest ten bize doğru property yi dönecek ayrı bir sınıf yazalım.

projemize ApiRequestSelector adında yapacağı iş aslında basit olan bir sınıf ekleyelim.

içeriği aşağıdaki gibi olacak. Bu generic sınıfımız dışarıdan kendisine verdiğimiz refit interface ini ve istediğimiz önceliği alarak, ApiRequest sınıfında tanımladığımız ilgili Lazy sınıfı bize dönecek sadece.


public class ApiRequestSelector<T>
    {
        private readonly PriorityType _priority;
        private readonly IApiRequest<T> _apiRequest;

        public ApiRequestSelector(IApiRequest<T> apiEndpoint, PriorityType priority)
        {
            _priority = priority;
            _apiRequest = apiEndpoint;
        }

        public T GetApiRequestByPriority()
        {
            T apiRequest;

            switch (_priority)
            {
                case PriorityType.Speculative:
                    apiRequest = _apiRequest.Speculative;
                    break;
                case PriorityType.UserInitiated:
                    apiRequest = _apiRequest.UserInitiated;
                    break;
                case PriorityType.Background:
                    apiRequest = _apiRequest.Background;
                    break;
                default:
                    apiRequest = _apiRequest.UserInitiated;
                    break;
            }

            return apiRequest;
        }
    }

Bu yadımcı sınıfımızı da yazdıktan sonra gelelim UserService kısmımızı yazmaya. Bunun için işe öncelikle service kısmında yapılacak işlemleri bir interface içerisinde toplarlayalım. IUserService isminde bir interface oluşturuyoruz. İçerisine sadece geriye Task içerisinde UserResponseModel model dönen ve adı GetUserList olan bir metod ekliyoruz. Bu metod bizden sadece Priority alacak, ve kaç user istediğimiz bilgisi ile sayfa numarası alacak.

  public interface IUserService
    {
        Task<UserResponseModel> GetUserList(PriorityType priorityType, int results, int page);
    }

Interface imizi tanımladıktan sonra bunun concrete class ını yani UserService sınıfını yazalım. UserService, IUserService interface inden türüyor yani yukarıdaki metodu implemente edip bir UserResponseModel dönmeliyiz. Ama bu işlemleri yaparken bu servis katmanımıza cache mekanizmamızı ve servisi çağırırken kullanmak istediğimiz Policy lerimizide ekleyelim. Service dışarıdan bir IApiRequest interface i alıyor. Bu interface önceki yazımda belirttiğim gibi fusillade ve modernhttpclient ın messagehandler ları ile oluşturulmuş bir refit HttpClient objesi dönüyor. Bu constructor parametresine IRandomUserEndpoint ini generic parametre olarak kullanacağım IApiRequest i geçmem yeterli.

Sonrasında Cache mekanizması için bana yardımı olacak bir metod yazıyorum. DeleteCache<T> metodu. Bu metod verdiğimiz generic parametre tipinde ne kadar obje var ise cache de yani akavache ile oluşturmuş olduğum sqlite da, hepsini uçuracak.

Sonrasında ise interface  imden gelen GetUserList metodunu implemente etmeye başlıyorum.

 public class UserService : IUserService
    {
        private readonly IApiRequest<IRandomUserEndpoint> _userEnpoint;

        public UserService(IApiRequest<IRandomUserEndpoint> userEndpoint)
        {
            _userEnpoint = userEndpoint;
        }

        public async Task<Unit> DeleteCache<T>() where T : new()
        {
            var result = await BlobCache.LocalMachine.InvalidateAllObjects<T>();
            return result;
        }

        public async Task<UserResponseModel> GetUserList(PriorityType priorityType, int results, int page)
        {
            var cacheKey = $"userRespo321213nse_{page}";
            var cachedCorporateList = BlobCache.LocalMachine.GetOrFetchObject(cacheKey, async () => await GetUserListAsync(priorityType, results, page), DateTimeOffset.Now.AddDays(7));
            UserResponseModel userResponseList = await cachedCorporateList.FirstOrDefaultAsync();

            if (userResponseList == null)
            {
                await BlobCache.LocalMachine.InvalidateObject<UserResponseModel>(cacheKey);
            }

            return userResponseList;
        }

        private async Task<UserResponseModel> GetUserListAsync(PriorityType priorityType, int results, int page)
        {
            UserResponseModel userResponseList = null;

            var apiRequestelector = new ApiRequestSelector<IRandomUserEndpoint>(_userEnpoint, priorityType);

            //Policy.Timeout(20);
            //Policy.Bulkhead(10, 10);
            userResponseList = await Policy
                                    .Handle<ApiException>(exception => exception.StatusCode == HttpStatusCode.ServiceUnavailable)
                                    //.CircuitBreakerAsync(2,TimeSpan.FromSeconds(5))
                                    //.AdvancedCircuitBreaker(failureThreshold: 0.5,
                                    //                        samplingDuration: TimeSpan.FromSeconds(10),
                                    //                        minimumThroughput: 1,
                                    //                        durationOfBreak: TimeSpan.FromSeconds(30))
                                    //.FallbackAsync((t) => Task.FromResult(new UserResponseModel()))
                                    .WaitAndRetryAsync(5, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
                                    .ExecuteAsync(async () => await apiRequestelector.GetApiRequestByPriority()?.GetUserList(results, page));

            return userResponseList;
        }
    }

Akavache nin Reqctive extension ile async-await mekanizmasını desteklediğiniz söylemiştik. Bu GetUserList metodunu implemente ederken, akavache nin, önceden bahsetmiş oduğumuz extension metodlarından biri olan GetOrFetchObject metodunu kullanıyorum. Bu metod benden 3 parametre alıyor ve şunları yapıyor.

  • verdiğim cache key parametresi ile, ilgili tipteki objeyi bu key ile db de arıyor. eğer bulursa, direkt olarak cache den bunu dönüyor, eğer bulamaz ise benden bir function istiyor ve bunu çalıştırıyor. bunun sonucunda aldığı result ı geri dönüp aynı zamanda da cache e yani db sine ekliyor.

Bu örnekte function delegate olarak GetUserListAsync adında aşağıda yazdığımız bir metodu gösterdik. Bu metod yine bir priortiy type alıyor,  ve aslında cache de data bulamadığımız durumda gerçekten api a request atacak olan metodumuz bu olduğu için, request i atmak için de ihtiyacımız olan page ve result parametlerini alıyor. Bu metod içerisinde hemen ilk olarak response tipimizi oluşturduktan sonra yaptığımız şey, yukarıda yazdığımız yardım metodu olan ApiRequestSelector sınıfımızı kullanarak hangi öncelikte giedecek olan request oluşturmak istediğimizi belirleyeceğimiz ve bize ilgili ApiRequest objesini dönecek olan metodu çağırıyoruz. Sonrasında iş tamamen Polly e kalıyor. Polly den elimden geldiğince uzunda bahsetmeye çalışmıştım ama bloglarda yazmakla bitmez yetenekleri. .burada sadece bu request i atmadan önce uygulamamızda istediğimiz senaryolara göre hangi Polly Policy lerini  kullanmak istediğimizi belirtip, en son ExecuteAsync içerisinde de, refit in bize vermiş olduğu GetUserList metodunu çağırıyoruzBu kısma biraz daha detaylı değinmek gerekirse;

  • var apiRequestelector = new ApiRequestSelector<IRandomUserEndpoint>(_userEnpoint, priorityType);
    • Bu satırda bize ApiRequest in önyüzden yolladığımız priorty ye göre karşılığı olan property si dönecek. HAtırlarsanız Lazy olarak tanımladığımız propertyler. Buradan bakarak hatırlayabilirsiniz.
  • userResponseList = await Policy .Handle<ApiException>(exception => exception.StatusCode == HttpStatusCode.ServiceUnavailable) //.CircuitBreakerAsync(2,TimeSpan.FromSeconds(5)) //.AdvancedCircuitBreaker(failureThreshold: 0.5, // samplingDuration: TimeSpan.FromSeconds(10), // minimumThroughput: 1, // durationOfBreak: TimeSpan.FromSeconds(30)) //.FallbackAsync((t) => Task.FromResult(new UserResponseModel())) .WaitAndRetryAsync(5, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))) .ExecuteAsync(async () => await apiRequestelector.GetApiRequestByPriority()?.GetUserList(results, page));
    • Burada ise birçok şey dönüyor. Polly nin;
      • ResultHandler
      • ErrorHandler
      • CircuitBreaker-AdvanceCİrcuitBreaker
      • WaitAndRetryAsync
      • Policy Execution ,
    • başıkları ile bahsettiğimiz kısımlarını görebilirsiniz. Detaylarına buradan ve buradan  bakabilirsiniz.

Peki bu kadar yazdıktan sonra gelelim artık önyüze. Birde araya mvvm kısmını katmıyorum, doğrudan MainPage.xaml sayfamızın constructor unda bu işlemleri yapmaya başlıyorum 🙂 (Normalde xaml sayfaların .cs tarafında mümkün olduğu kadarı ile kod görmemek, sadece InitializComponent görmek isteriz, diğer herşeyi herbir sayfanın kendi ViewModel lerinde görmek isteriz, ama bu ayrı bir konu ve zaten mvvm de uyulanması zorunluk bşr pattern değil xamarin projeleriniz için.)

aşağıda constructor metodunda yazdığımız kod bu şekilde.


Task.Run(async () =>
            {
                var service = new UserService(new ApiRequest<IRandomUserEndpoint>());
                var result = await service.GetUserList(PriorityType.UserInitiated , 10, 1);

                Device.BeginInvokeOnMainThread(() =>
                {
                    UserList = new ObservableCollection();
                    if (result != null && result.results.Count > 0)
                    {
                        foreach (var item in result.results)
                        {
                            UserList.Add(item);
                        }
                    }
                    lstView.ItemsSource = UserList;
                });
            });
        }

Aslında kaç yazıdır yazdığımız herşeyi kullanmaya kalktığımız da kapladıkalrı satır sayısı sadece 2 🙂

  • var service = new UserService(new ApiRequest<IRandomUserEndpoint>());
    • (evet hardcoded initialization var, bir IoC container bağlayabiliriz ama kalsın şimdilik – o zaman o kadar interface i niye yazdın demeyin buraya odaklanın 🙂 .. ) UserService objemizi ve bizden istediği ApiRequest sınıfımızı kullanacak olduğumuz refit interfaceini generic parametre göstererek veriyoruz.
  • var result = await service.GetUserList(PriorityType.Speculative, 10, 1);
    • sonrasında ise sadece refit in bize yaşayan bir metod olarak sunduğu bizim interface de tanımladığımız GetUserList metodunu UserInitiated önceliği ile çağırıyoruz. 

Biz bu iki satırı yazarken arkada dönen devran ile ilgili artık ciddi anlamda bir fikrimiz var, ve ilk postta dediğim gibi, sadece bir using bloğu içerisinde bir httpclient oluşturup, url i verip GetStringAsync vb.. metodlar ile bu işleri yapmak ile aramızda dağlar ovalar var artık. Gerçek anlamda Resilient bir Network katmanız var diyebiliriz. Daha gelişememiz mi, tabii ki gelişir sonu yok, hatası var mıdır illa ki vardır bunun da sonu yok :). ilerlyen günlerde, bu yapının HttpClient ile değilde Asp.Net Core 2.1 ile gelen HttpClientFactory ile nasıl yaparız detaylıca bahsetmek istiyorum.

Umarım faydalı olmuştur.

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

Resilient Network Services Bölüm 7 – Xamarin Projemizde Refit, Polly, Akavache, Fusillade ve ModernHttpClient Birlikte Kullanımı

Selamlar,

Bu seri boyunca bahsettiğim bu güzel kütüphaneleri artık iş üzerinde görme vakti geldi 🙂 Tüm bu kütüphanelerin bir xamarin projesinde Resilient Network services oluşturulması için nasıl bir arada kullanıcağımızı göreceğimiz bu yazımızda, kütüphanelerin detaylarını görmek için önceki yazıları da okumanızı öneririm. Buradan başlayabilirsiniz.

Refit, Fusillade, ModernHttpClient, Akavache ve Polly  kütüphanelerinin herbirinin birbirinden farklı ve aynı zamanda aynı işlevi gören özellikleri mevcut.
Öncelikle hangi kütüphaneden hangi özellikleri alıp nasıl bir yapı kuracağımızı ve bunu xamarin projelerinde nasıl kullanacağımıza bakacağız. Sırası ile kütüphanelerden alacağımız özellikler şunla olacak.

  1. Refit: Tüm Api haberleşmesini, tek bir interface altında toplayıp, HttpClient ve HttpMessageHandler yönetimini runtime da bu arkadaşa bırakacağız. Herhangi bir yerde biz HttpClient sınıfı ile ilgilenmeyeceğiz.
  2. Akavache: Api dan gelen responlarımız için tüm objelerimizi Akavache ile sqlite3, lokal veritabanında saklayacağız. Request atmaya ne zaman gitmeli, cache de varsa önce oradan okuma işlemi yada cache de olsa bile cache den sonucu hemen dönüp, yine de arka planda Api ye gidip cache deki dataları yenileme gibi işlemleri akavache ile yapacağız.AKavache de bize performanslı ve kolay bir SQLite yönetimi sunacak
  3. Fusillade: Bu güzel kütüphaneyi requestleri önceliklendirme işlemlerimiz için ve Request Limit işlemi için kullanacağız. Kullanıcı tetiklemesiyle mi birşeyler oldu, bir arka plan request imi atıyoruz, yoksa speculative bir request atıp verdiğimiz sınır boyunca belli bir data mı çekiyoruz bunu söyleyeceğiz.
  4. ModerHttpClient: En basit kullanımı olan bu kütüphanenin amacı zaten belli, daha performanslı http haberleşmesi sağlamak. kendi ardaka kullandığı native framework ler ile, bize sunduğu NativeMessageHandler ile daha performanslı network haberleşmesi sağlayacak.
  5. Polly: Bunu kullanın kullanabildiğiniz kadar 🙂 Çok fazla özelliği olan bu kütüphane de genellikle kullanılacak özellikler, api hanerleşmesinde istediğimiz senaryolara bağlı olduğundan, biz default olarak, Transient yani anlık hatalar için Retry pattern ini requestlerimizde uygulamak için kullanacağız.
    Daha da detaylarını eski yazılarımdan ve kütüphanelerin kendi github repo larından edinebilirsiniz.

Örnek olması açısından public api lardan biri randomuser.me  kullanalım. adrese gittiğinizde api ın neler sağladığını bulabilirsiniz. Biz basit bir get request i ile işe başlayalım. REquest atacağımız adres ;

https://randomuser.me/api/?results=5

Bu adres bize results querys string ile verdiğimiz sayı kadar user geriye dönecektir. Bu user a bağlı birden çok property ve ayrı class lar var. Postman üzerinden yada direk browserdan bu adrese request attığınızda karşınıza çıkacak olan response şu şekilde olacaktır.

Capture.PNG

bu json ın c# tipinde nasıl bir class a karşılık geldiğini anlamak ve yazmak için uzun uzun bakmak yerine sıkça kullandığım jsonutils.com adresine json ı yapıştıralım ve bize bu json olarak aldığımız response C# ile oluşturulmuş class lar olarak versin.

jsonutils.com a gidip aşağıdaki gibi json text alanına json string imizi girdiğimizde istediğimiz isimle ana sınıfı oluşturup buna bağlı sınıflarıda kendisi oluşturuyor.

Capture.PNG

bu endpoint e attığımız request in C# cası jsonutils in bize söylediğine göre tam olarak aşağıdaki gibi;

   public class Name
    {
        public string title { get; set; }
        public string first { get; set; }
        public string last { get; set; }
    }      
 
    public class Coordinates
    {
        public string latitude { get; set; }
        public string longitude { get; set; }
    }

    public class Timezone
    {
        public string offset { get; set; }
        public string description { get; set; }
    }

    public class Location
    {
        public string street { get; set; }
        public string city { get; set; }
        public string state { get; set; }
        public object postcode { get; set; }
        public Coordinates coordinates { get; set; }
        public Timezone timezone { get; set; }
    }

    public class Login
    {
        public string uuid { get; set; }
        public string username { get; set; }
        public string password { get; set; }
        public string salt { get; set; }
        public string md5 { get; set; }
        public string sha1 { get; set; }
        public string sha256 { get; set; }
    }

    public class Dob
    {
        public DateTime date { get; set; }
        public int age { get; set; }
    }

    public class Registered
    {
        public DateTime date { get; set; }
        public int age { get; set; }
    }

    public class Id
    {
        public string name { get; set; }
        public string value { get; set; }
    }

    public class Picture
    {
        public string large { get; set; }
        public string medium { get; set; }
        public string thumbnail { get; set; }
    }

    public class Result
    {
        public string gender { get; set; }
        public Name name { get; set; }
        public Location location { get; set; }
        public string email { get; set; }
        public Login login { get; set; }
        public Dob dob { get; set; }
        public Registered registered { get; set; }
        public string phone { get; set; }
        public string cell { get; set; }
        public Id id { get; set; }
        public Picture picture { get; set; }
        public string nat { get; set; }
    }

    public class Info
    {
        public string seed { get; set; }
        public int results { get; set; }
        public int page { get; set; }
        public string version { get; set; }
    }

    public class User
    {
        public IList results { get; set; }
        public Info info { get; set; }
    }

Bu class ları projemizde Models klasörüne (yoksa bir tane Model adında bir klasör açarak) içerisine koyalım. Diğer bir önz hazırlığımız da Fusillade tarafında bize önyüzde ve ortak kod tarafında yardımcı olması için, PriorityType adında bir enum oluşturalım.

public enum PriorityType
{
   Background,

   Speculative,

   UserInitiated
}

Hazırlığımıza devam edelim. Tüm request tiplerimizi bir arada tutacağımız bir ApiRequest interface i oluşturuyorum. Daha sonra uygulama üzerinden gidecek olan tüm requestleri bu interface i implemente eden ve başka türlü çalışmasını istediğimiz bir şekle çevirmek istersek bu şekilde ilerleek bize fayda sağlıyacaktır.

 public interface IApiRequest
    {
        T Speculative { get; }
        T UserInitiated { get; }
        T Background { get; }
    }

Interface in sadece üç adet readonly propertysi mevcut. Bundan sonra bu Interface i implemente edecek, concrete ApiRequest sınıfımızı oluşturacağız ve Fusillade,Refit ve ModernHttpClient kütüphanelerini kullanmaya başlayacağız.

Bu yazıyı burada bırakıyorum. Hazırlık kısmı olarak biraz fazla uzadı. Buradaki adımları yaptığımızda bir sonraki yazımda asıl implementasyonu benimle beraber siz de yapabilirsiniz.

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

 

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 – 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.

Microsoft Xamarin Istanbul Development Meetup “Xamarin Resilient Network Services Bölüm 1”

Herkese Selamlar,  biraz geç olsa da 25 ekim günü Microsoft Türkiye‘de yaptığımız etkinlikteki konuyla ilgili yazma fırsatı ancak bulabiliyorum. Güzel bir etkinlik günü geçirdik, 3 farklı değerli arkadaşımla beraber aşağıdaki konulara değinmiştik.

• .Net Core ile Dependency Injection (Özgür Kaplan)
Xamarin ile Dependency Injection (Yiğit Özaksüt)
Xamarin ile Resilient Network Services (bunu ben anlattım ve burada da uzunca değineceğim detaylarına.)
C# hakkında doğru bilinen yanlışlar ve performans ipuçları (Cihan Yakar)

Benim anlattığım Xamarin ile Resilient Network Services sunumum ile ilgili proje ve dosyaları buradan inceleyebilirsiniz.

Katılan dinleyici ve konuşmacı tüm arkadaşlara teşekkür ederek kendi konumun detaylarına giriş yapıyorum.

Bu konu ile ilgili aslında yazmak istediğim birkaç blog serisi var. Aslında genel bakış açısını burada belirtip detaylarına diğer blog postlarımda değineceğim.

İlk olarak şu Resilient(Esnek) kelimesinden başlayalım. Ne demek bir yapının esnek olması. Bu aslında şu demek;

  • Kurduğumuz yapı, belli hatalar karşısında nasıl davranacığını önceden bilen, öncelikleri belli ve değiştirilebilir esnek kırılmaz bir yapı olmalı.

Bu yapı, diğer tüm yazılım projelerinde, uygulamayı mümkün olduğunca sağlıklı bir şekilde ayakta tutmak için kullanılan mimari bölümlerin(Ölçeklenebilir olması, esnek olması, hızlı olması, güvenilir olması vs vs..) küçük bir parçası aslında.  Bu sistemin ben yazılım projelerinin olmaz ise olması Networking üzerinde anlattım, çünkü danışmanlıklarımda gördüğüm ve eğitimler de hissettiğim en büyük açık burada mevcut idi.


  using (var client = new HttpClient())
  {
    client.BaseAddress = new Uri("https://randomuser.me/api/");
    var content = await client.GetStringAsync("?results=10&page=1");
    var result = JsonConvert.DeserializeObject<ResponseModel>(content);
  }

Yıkarıda ki kod bloğunu bir bakalım. Birçok projede bir API haberleşmesi için gördüğüm kod bu kadar da kalıyor. Daha üzerine düşünülmüş olanlarda ise bu şekilde bir kullanım generic bir class içerisine yerleştirilmiş ve onun içerisinden haberleşme çağırılıyor oluyor. Fakat içeriği pek de değişmiyor. Peki bu koddaki yanlış nedir? Yada var mıdır?

Benim gördüğüm kadarı ile şöyle;

Koda ilk bakışta bir yanlış gözükmüyor olabilir. Evet derlenir ve çalışır da, hatta herşey iyi giderse response u alıp gerekli çevirme işlemlerini de yapar. Ama işte bu kod bloğunda birçok şey yolunda gider varsayılmış. Http client objesinin static tanımlanmamış olması ve bunun sıkıntılarına başka zaman değineceğim bunu şimdilik göz ardı edelim diğer konulara bakalım.

  • Internette bir sorun olduğunda bu request ne olacak
  • Server dan 500 aldığında request im ne yapmalı,  503 olduğunda ne olmalı.
  • Time out yersem ne yapmalıyım? yeniden göndermeli miyim requesti? Eğer gönderecek isem kaç kere göndermeliyim, ne kadar ara vermeliyim?
  • Response cachlenebilir bir response mu eğer öyle ise bunu da devreye koysam güzel olmaz mı?
  • Bu request uygulama içerisinde gidecek olan diğer requestlere göre bir farkı önceliği var mı?

gibi gibi sorular aklıma geliyor. İşte tüm bu konular üzerinden bir xamarin projesinde kullanmamız ve güzel bir networking altyapısı kurmanız için size 5 farklı ve değerli kütüphaneden bahsedeceğim. Ve bunların hepbirden nasıl uyumlu olarak kullanabiliriz buna değineceğim.

Kütpühaneleri şunlar;

  • Refit: The automatic type-safe REST library for .NET Core, Xamarin and .Net
  • Fusillade: An opinionated Http library for mobile development
  • ModernHttpClientPlatform spesific networking libraries to Xamarin applications
  • Akavache: An Asynchronous Key-Value Store for Native Applications
  • Polly: .Net resilience and transient-fault-handling  library

kısaca açıklamlarını direk kendi github adreslerindeki gibi yazdım kenarlarına. Detaylarına ve örneklerine sırası ile diğer postlarımda başlayacağım. Hepsinden bahsettikten sonra da bir arada bir xamarin projesinde nasıl kullanılırız buna bakacağız.

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