Erhan Ballıeker

Azure Machine Learning Studio ile Machine Learning’e Giriş

Selamlar,

20 Ekim Cumartesi günü Microsoft Türkiye’de gerçekleştirmiş olduğumuz .NetKonf Türkiye etkinliğinde bir çok değerli konuya değindik. İki farklı sunumda iki farklı konu paylaştım gelen arkadaşlarımızla. Çok keyifli geçen bu etkinliğin ardından anlattıklarımı buraya da daha detaylı olarak yazmak istedim.

Yaptığım iki sunum şunlardı;

  • Machine Learning with Azure Machıne Learnıng Studio
  • Xamarin forms 3.0-1-2-3 features and samples

Bu iki konudan ilkine bakalım bu yazımızda. Bugünlerde artık çokça Machine Learning, AI, DeepLearning vs vs terimlerini duymaya başladığınıza eminim. Birçok ürün herhangi bir reklamında bu terimlerle yer vermeden geçmiyorlar. Peki bizde bir data scientist olmadan kendi projelerimizde ML çözümleri uygulayamaz mıyız?

Tabii ki uygulayabiliriz. Bilgi düzeyimiz bir DataScientist insanının matematik, istatistik ve veri analizi kadar olmasa bile, şunu söyleyebilirim ki, elimizde yeterli derece de analiz edip tahminler yürütmek istediğimiz veriler var ise, kendi uygulamamıza da bu ML çözümlerini bir özellik katmanın özellikle Azure Machine Learning ile ne kadar olduğunu görünce çok şaşıracaksınız.

Tabii hiç bilmeden de olmaz değilmi, bir AI uzmanı olmasak da, öncelikle bu yazımızda bu Machine Learning denen şey nedir, ne değildir bundan bahsedelim. Sonra Azure un bize sağladığı kolaylıkları ve çözümleri görelim.

Machine Learning tam olarak şudur ;

Capture

:). Komik gelebilir, ama akademik camia da insanlar senelerdir matematik ve istatistik bilimi üzerinde çalışarak birçok algoritma geliştirdiler zaten. Bu ML in son zamanlarda çok popüler olmasının temel 3 sebebi var;

  1. Artık çok sayıda denemeler yapıp sonuçlar elde edeceğimiz algoritmalar oluşmuş ve bunlar denenmiş sonuç alınmış durumda
  2. Artık bilgisayarlar eskisinden çok daha güçlü, bu algoritmalar, belli problemler üzerinde çalıştırılarak çeşitli simülasyon ve analizler yapmak çok daha masrafsız hale gelmiş olmakta.
  3. Artık çok çok fazla data birikmiş durumda. Her konuda etrafımızda tonlarca data mevcut diyebiliriz.

Bu 3 temel madde bir araya geldi mi artık MachineLearning konuşmakta kaçınılmaz oluyor.

Peki yazının başında bahsettiğim gibi, Machine Learning ile beraber duymaya başlamış olduğunuz daha başka terimler de mevcuttur tahminimce

bunlar;

  • Artificial Neural Networks
  • Deep Learning
  • Bayesian Networks
  • Artificial Intelligence

gibi bu liste uzar bile. Peki bunlar nerede çakışır? çakışır mı? Daha büyük resme baktığımız zaman ne nereye oturur buna bakalım.

Capture.PNG

Yukarıdaki resimde kısaca özetlenmiş bir hali mevcut. Aslında bakarsanız özet şu;

  • DeepLearning, Artificial N. Networks, vs vs hepsi özelinde Machine Learning kullanan ve farklı algoritmalar ile farklı sorunlara farklı yaklaşımlar getiren birer özel çözümlerdir. Ve tüm bu çözümler kendi içerisinde Machine Learning kullanırken, nihai amaçları ise, herşeyin özel çözümleri ile sonuçlanabilir olduğu bir Yapay Zeka (Artificial Intelligence) üretme çabasıdır.

Peki, madem artık biliyoruz ki tüm duyduğumuz terimler içerisinde Machine Learning daha da core bir seviyede kullanılıyor. O zaman şunu anlamaya çalışalım. Machine Learning ne demek?

Aslında biraz süslü bu tanımın arkasında yatan şey, insan gözüyle ve hızıyla bakıp çıkarımlar yapmamızın çok uzun süreceği şeyleri, bilgisayara aktarıp onun yapmasını sağlamak.

Capture.PNG

Yukarıda ki resme bir bakalım. Elimizde olan bu kredi kartı satışlarının fraud(sahte-hileli) olup olmama durumunu içeren kayıtlar sadece bu kadar olsa, bu datalara bakıp bir süre sonra birtakım çıkarımlarda bulunabilirdik değil mi?

Örneğin;

  • 20 ile 30 yaşları arasında, Amerika da verilmiş, Rusyada kullanılmış ve 1000$ üzerinde olan işlemlerde fraud olma riski yüksek diyebilirdik.

Ama bu datanın milyonlarca kayıttan oluştuğunu düşünün. Üzerine bir de bu kadar az kolon yerine onlarca kolondan oluşan farklı detaylar içeren kayıtlar olduğunu düşünün.

Bu durumda bir yada onlarca insanın bu datalara bakıp çıkarımlar yapması yerine, yıllardır geliştirilmiş algoritmaları ve günümüzün güçlü bilgisayarlarını kullanıp bu işi onlara bıraksak daha kolay olmaz ve kısa sürmez mi? Şüphesiz. İşte tam da bu işi bilgisayarlara bırakalım o zaman dediğimiz noktada Machine Learning devreye girmiş oluyor.

Peki bir de büyük resimde bu Machine Learning nasıl işliyor buna bakalım.

Capture.PNG

Aslında en üstten baktığımızda tüm süreç bu kadar desek yanlış olmaz 🙂 tabii her zaman olduğu gibi detaylara indikçe boğulmalar olacaktır.  Ama özetle olay şu;

  • Elimizde bir tek bir kaynaktan yada farklı kaynaklarda olan takım datalar var. Bunları bir araya getirip incelemek istediğimiz tek bir data set i olarak birleştirelim. (Burası başlı başına olay zaten, sadece bunun için bile bir ton tool var)
  • Daha sonra bizim yukarıdaki örnekte, insan gözüyle bir kaç saniyede yaptığımız gibi, bazı benzerlikler bulmaya çalışalım bu data içerisinde. Bunu yaparken de var olan onlarca data algoritmayı deneyerek başlayabiliriz.
  • Artık bir benzerlik bulduğumuzu düşündüğümüz anda, bunu bir model olarak benimseyelim ve bundan sonraki datalara bakarken dataların bu modele e ne kadar uyup uymadıklarına bakalım.

Tüm süreç dediğimiz gibi aslında en dışarıdan bakıldığında bu kadar. Ama bu sürecin kendisi tamamen kendini tekrar eden bir süreç. Bunu unutmamak lazım. Yani bir Machine Learning Projesi, hiçbir zaman tam olarak bitti diyemezsiniz, çünkü yeni veri akışı oldukça, sizin data setleriniz değiştikçe, uyguladığınız algoritmalar, bulduğunuz benzerlikler ve nihayetinde ortaya çıkan modeller değiştikçe tum bu yukardıdaki resimdeki süreç aslında başa sarıp sarıp duracaktır. Yani sürecin kendisi tamamen iterative – kendini tekrar eden- bir süreçtir.

Günümüzde artık İş adamlarıi yazılımcılar, datascientist ler vs neredeyse herkes bu ML işine, işinin bir kısmında bir takım sorunlarına çözüm bulmak için ihtiyaç duyuyor.

Bu kadar çok adı geçmiş ken bir de şuna bakalım. Kim bu data scientist?

Data Scientist olmanız için 3 temel şart vardır diyebiliriz.

  • İstatistik ve haliyle Matematik Bilgisi
  • Programlama Bilgisi
  • Belli bi Domain de (örn; bankacılık) iş anlamında yüksek derecede bilgi.

ilk iki madde olsa bile kendinize ben her konuda data scientist im demek, bir api yazıp, html editleyip, kendinize ben fullstack developer ım demek gibi olabilir. Çünkü veriyi yorumlayacığınız alanda bilgi sahibi olmak size, hangi algoritmaları seçmeniz gerektğinden hangi data setleri ile çalışmanız gerektiğine kadar birçok alanda fayda sağlayacaktır.

Peki, yazının başında dediğimiz gibi bir data Scientist olmadan da ML çözümleri üretemez miyiz üretebiliriz. Cloud ortamlarda bunu bize servis olarak sunan büyük küçük birçok vendor var.

Capture.PNG

Yukarıda gördüğünüz gibi Clouda olarak bu hizmeti veren en büyük iki firma Microsoft ve Amazon. Biz bunlarda Azure üzerinde neler yapabileceğimize bakacağız.

Bir ML projesinin temel düzeyde ne aşamalardan oluştuğunu gördüğümüze göre Azure tarafında büyük resimde işler nasıl işliyor buna bakalım.

Capture.PNG

Birçok farklı kaynaktan datanızı Machine Learning Studio üzerinde sürükle bırak (evet yanlış okumadınız baya sürükle bırak=)) yöntemiyle belli algoritmalar üzerinden geçirip, ortaya bir iki tıkla bir web service çıkartıyorsunuz. Ve bu servisi Web- mobil, yada diğer ihtiyaç duyduğunuz yerlerde kullanmaya başlıyorsunuz.

Bu yazıyı burada noktalıyorum. Bir sonraki yazımda Machine Learning in biraz da terimsel taraflarına bakıp. Azure tarafına giriş yapalım.

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

ASP.NET Core da çoklu Environment(Ortam) Konfigürasyonu ve Kullanımı

Selamlar,

Bu yazımda sizlere bir asp.net core projenizde çoklu ortam oluşturma ve bunları kullanmak hakkında bilgiler vereceğim.

Asp.Net Core uygulamanızın davranışını runtime ortamı için set etmiş olduğunuz environment variable ile belirler. Hiç set etmemiş olmanı durumunda otomatik olarak bu değer “Production” olarak kabul edilir.

Bir asp.net core uygulaması nasıl ayağa kalkar, ilk başta neler olur ve request browserdan sizin external(IIS, Apache, Nginx vs.) ve internal(Kestrel vs.) sunucularınıza gelip ne şekilde aşamalardan geçip geri browser a döner bundan bahsetmiştik. Buradan ve buradan okuyabilirsiniz. Uygulamanın startup anında Asp.Net Core, ASPNETCORE_ENVIRONMENT global variable ının değerini okur ve IHostingEnvironment.EnvironmentName propertysine set eder.

Assembly:Microsoft.AspNetCore.Hosting.Abstractions.dll

public string EnvironmentName { get; set; }

Bu değere herhangi birşey set edebilirsiniz, 3 adet değer framework ile beraber geliyor zaten. Çoğunlukla bunlar işinizi görmenize yetecektir. Bunlar aynı assembly altında bulunan

public static readonly string Development;

public static readonly string Staging;

public static readonly string Production;

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    if (env.IsProduction() || env.IsStaging() || env.IsEnvironment("Custom_Stage"))
    {
        app.UseExceptionHandler("/Error");
    }

    app.UseStaticFiles();
    app.UseMvc();
}

Bir startup dosyasında yukarıdakine benzer bir kod illaki görmüşsünüzdür. Burada söylenen şey aslında şudur.

  • Eğer ASPNETCORE_ENVIRONMENT global variable ın değer “Development” ise UseDeveloperExceptionPage middleware inin, request akışı sırasında ilk olarak devreye alınması. Dolayısı ile, uygulama ayağa kalktıktan sonra request öncelikle buradan geçecek ve yine en son bu middleware den geçip broweser a doğru yola çıkacak.
  • Eğer environment “Production”, “Staging” yada bizim konfigürasyon dosyasında custom tanımladığımız bir “Custom_Stage”  değerine eşit ise, o zaman da UseExceptionHandler middleware i request in akışı süresince devreye girecek olan ilk middleware olacak.

Uygulama akışını konfigüre etmek için environment değerlerini set edeceğimiz yer, lokal development için Properties\launchSettings.json dosyasıdır.  Burada set ettiğimiz environement değerleri System Environment değerlerini ezecektir. Aşağıda bir adet launchSettings.json dosyasını inceleyelim.

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:54339/",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_My_Environment": "1",
        "ASPNETCORE_DETAILEDERRORS": "1",
        "ASPNETCORE_ENVIRONMENT": "Staging"
      }
    },
    "EnvironmentsSample": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Staging"
      },
      "applicationUrl": "http://localhost:54340/"
    },
    "Kestrel Staging": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_My_Environment": "1",
        "ASPNETCORE_DETAILEDERRORS": "1",
        "ASPNETCORE_ENVIRONMENT": "Staging"
      },
      "applicationUrl": "http://localhost:51997/"
    }
  }
}

Buradaki launchSettings dosyamızda 3 farklı profile görüyoruz. Uygulama dotnet run komutu ile ayağa kalktığında ilk profile seçilir ve uygulama ayağa kalkar.

Burada “commandName” : “Project” kısmı önemlidir. commandName;

  • IISExpress
  • IIS
  • Project (External Server olmadan doğrudan Kestrel ayağa kalkar)

bu üç değerden biri set edilebilir.

Bu şekilde uygulamayı çalıştırdığımızda aşağıdaki gibi bir çıktı alırız.,

PS C:\Websites\EnvironmentsSample> dotnet run

Using launch settings from C:\Websites\EnvironmentsSample\Properties\launchSettings.json…

Hosting environment: Staging

Content root path: C:\Websites\EnvironmentsSample

Now listening on: http://localhost:54340

Application started. Press Ctrl+C to shut down.

Visual Studio üzerinden bu launchSettings dosyamızda verdiğimiz profile ları görüp yönetebiliriz. Projenin özelliklerine geldiğimizde Debug tabını açtığımızda, dosyamızda ki değerler burada karşımıza çıkar. Uygulamanın kullanmasını istediğimiz Profile ı buradan da değiştirebiliriz. Ama buradan yaptığımız değişikliklerin doğrudan etkili olması için en azından Kestrel restart olması şart. Restart işleminden sonra kestrel environment taki değişikliklere göre hareket etmeye başlayacaktır.

Capture.PNG

ASPNETCORE_ENVIRONMENT global environment ını daha global olarak set etmenin birden farklı platformlarda birden çok yolu var. Bunlar şöyle ;

  • Windows Command Promt — (current session – uygulamayı dotnet run demeden önce, o çalışma için)
    • set ASPNETCORE_ENVIRONMENT=Development
  • PowerShell — (current session – uygulamayı dotnet run demen önce, o çalışma için)
    • $Env:ASPNETCORE_ENVIRONMENT = “Development”
  • Windows Globally Command Promt
    • setx ASPNETCORE_ENVIRONMENT Development /M
  • PowerShell Windows Globally
    • [Environment]::SetEnvironmentVariable(“ASPNETCORE_ENVIRONMENT”, “Development”, “Machine”)
  • macOS

    • Uygulamayı çalıştırma aşamasında söylemek için
      • ASPNETCORE_ENVIRONMENT=Development dotnet run
    • Çalıştırma öncesi
      • export ASPNETCORE_ENVIRONMENT=Development
    • Machine level seviyesinde set etmek için
      • export ASPNETCORE_ENVIRONMENT=Development

Environment-based Startup Class

Kullanımını çok az görmüş olsam da uygulamamızdaki startup dosyalarını da environment variable a bağlı olarak çalışabilecekleri şekilde farklı isimlerde oluşturup, ilgili environment variable altında ilgili startp class ının ve onun metodlarının devreye girmelerini sağlayabiliriz.

Startup{EnvironmentName} şeklinde farklı startup dosyaları oluşturduğumuzda frameowork otomatik olarak ayağa kalktığı andaki environmentname ile biten bir startup dosyası varsa onu çalıştıracaktır.

// Startup class to use in the Development environment
public class StartupDevelopment
{
    public void ConfigureServices(IServiceCollection services)
    {
        ...
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        ...
    }
}

// Startup class to use in the Production environment
public class StartupProduction
{
    public void ConfigureServices(IServiceCollection services)
    {
        ...
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        ...
    }
}

// Fallback Startup class
// Selected if the environment doesn't match a Startup{EnvironmentName} class
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        ...
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        ...
    }
}

Bunun devreye girmesi için IWebHostBUilder tarafında UseStartup metodunun string parametre olarak assembly name alan overload unu kullanmak yeterli.

public static void Main(string[] args)
{
    CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
    var assemblyName = typeof(Startup).GetTypeInfo().Assembly.FullName;

    return WebHost.CreateDefaultBuilder(args)
        .UseStartup(assemblyName);
}

Yukarıdaki gibi her bir environment için yeni bir startup dosyası oluşturmak istemez isek, tekbir startup dosyasına, aynı mantıkla metodların isimlerinin arasına EnvironmentName adını koyarsak ayağa kalkış anındaki environment name e sahip oklan ConfigureStaging.. ve ConfigureStagingervices.. metodları çalışacaktır. Aynı isme sahip bir metod bulunamazsa default olarak Configure ve ConfigureServices metodları çalışacaktır.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        StartupConfigureServices(services);
    }

    public void ConfigureStagingServices(IServiceCollection services)
    {
        StartupConfigureServices(services);
    }

    private void StartupConfigureServices(IServiceCollection services)
    {
        services.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        if (env.IsProduction() || env.IsStaging() || env.IsEnvironment("Staging_2"))
        {
            app.UseExceptionHandler("/Error");
        }

        app.UseStaticFiles();
        app.UseMvc();
    }

    public void ConfigureStaging(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (!env.IsStaging())
        {
            throw new Exception("Not staging.");
        }

        app.UseExceptionHandler("/Error");
        app.UseStaticFiles();
        app.UseMvc();
    }
}

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ı 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ı Bölüm 2

Selamlar,

Bir önceki yazımda önhazırlığını tamamladığımız yazımızın asıl kısmına geçelim.  En son IApiRequest adında bir interface oluşuturarak içerisine 3 adet readonly property koymuştuk. Şimdi hızlı bu interface implemente edecek olan gerçek ApiRequest sınıfımızı oluşturalım ve ilgili kütüphaneleri kullanmaya başlayalım.

ApiRequest adında bir class ekleyelim projemize, içeriği aşağıdaki şekilde olacak.

public class ApiRequest : IApiRequest
    {
        private readonly Lazy _background;
        private readonly Lazy _userInitiated;
        private readonly Lazy _speculative;

        public string BaseApiAddress = "https://randomuser.me/api";

        public ApiRequest()
        {
            _background = new Lazy(() => CreateClient(new RateLimitedHttpMessageHandler(new NativeMessageHandler(), Priority.Background), BaseApiAddress));
            _userInitiated = new Lazy(() => CreateClient(new RateLimitedHttpMessageHandler(new NativeMessageHandler(), Priority.UserInitiated), BaseApiAddress));
            _speculative = new Lazy(() => CreateClient(new RateLimitedHttpMessageHandler(new NativeMessageHandler(), Priority.Speculative), BaseApiAddress));
        }

        public T Background => _background.Value;

        public T Speculative => _speculative.Value;

        public T UserInitiated => _userInitiated.Value;

        public T CreateClient(HttpMessageHandler handler, string baseApiAddress = null)
        {
            var client = new HttpClient(handler)
            {
                BaseAddress = new Uri(baseApiAddress ?? BaseApiAddress)
            };

            var res = RestService.For(client);
            return res;
        }
    }

Peki gelelim yukarıda ki class ımızı satır satır inceleyelim. Bu class ımızda bu seri boyunca bahsettiğimiz kütüphanelerden hali hazırda 3 tanesini kullanmış olduk. Bunlar Fusillade, Refit ve ModernHttpClient. Sınıfımız incelersek şunları görüyoruz.

  • class ımız IApiRequest Interfaceinden türüyor, ve buradan alması gereken propertyler aşağıdaki gibi oluşturuldu.
    • public T Background => _background.Value;
    • public T Speculative => _speculative.Value;
    • public T UserInitiated => _userInitiated.Value;
  • Yukarıda bu propertylerin initalize edilme kısmını C# ın Lazy objesine bıraktık. 3 adet field ımızı class ilk başında tanımlandı. Bunların tipi Lazy olarak işaretlendi. Ve ApiRequest sınıfmızın constructor nda da bu Lazy field ların neyi ne şekilde initiazlize edileceklerini tanımladık;
    • 3 Lazy adet field ımız
      • private readonly Lazy _background;
      • private readonly Lazy _userInitiated;
      • private readonly Lazy _speculative;
    • Constructor da bunların yine Lazy sınıfının constructor ını kullanarak ne şekilde ne döneceklerini tanımladık. Aşağıda yazmış olduğumuz CreateClient metodu ile geriye sınıfı oluştururken verdiğimiz generic parametresini geri dönüyoruz.  Constructor da kullandığımız bu RateLimitiedHttpMessageHandler ve NativeMessageHandler a dikkat edelim.YAzmış olduğumuz CreateClient metodu aslında bizim için bir HttpClien oluşturacak bunu biliyoruz. Parametre olarak bu method bizden bir HttpMessageHandler istiyor.  Bu metoda Fusillade in RateLimitedHttpMessageHandler ını veriyoruz ve priority oralark da her bir interface imizden gelen her bir property karşılık olan Fusillade Priortiy lerini veriyoruz.  RateLimitedHttpMessageHandler  içerisine contructor parametresi olarak bir de ModernHttpClient ın  NativeMessageHandler ını veriyoruz. Bu sayede en alttaki CreateClient metodumuza geçmiş olduğumuz HttpMessageHandler, ModerHttpclient ın handler ını kullanan Fusillade in RateLimited handler ı olmuş oldu. Bu ikisini bir arada bu şekilde kullanabiliriz.
      • _background = new Lazy(() => CreateClient(new RateLimitedHttpMessageHandler(new NativeMessageHandler(), Priority.Background), BaseApiAddress));
      • _userInitiated = new Lazy(() => CreateClient(new RateLimitedHttpMessageHandler(new NativeMessageHandler(), Priority.UserInitiated), BaseApiAddress));
      • _speculative = new Lazy(() => CreateClient(new RateLimitedHttpMessageHandler(new NativeMessageHandler(), Priority.Speculative), BaseApiAddress));
    • Interface den gelen Propertylerimiz class ımız üzerinden seçildiğinde, LAzy sınıfı bizim ona verdiğimiz delegate i kullanarak bu sınıfı oluştururken verdiğimiz generic T objesini bizim için oluşturacak.
  • CreatClient metoduna bakalım.
    • var client = new HttpClient(handler)
    • { BaseAddress = new Uri(baseApiAddress ?? BaseApiAddress) };
      • Burada yeni HttpClient objesi oluşturup bunun runtime da bizim istediğimiz yaşayan Refit interface ini alıp haberleşme yapısı olarak geri vermesi için Refit in RestService.For generic metodu nu kullanıp içeriye client ımızı veriyoruz.
    • var res = RestService.For(client); return res;

Peki hemen bir de madem Refit i de kullandık, ilk yazıda belirttiğim bu endpoint ile haberleşecek ve userları bize getirecek olan refit Interface imizi oluşturalım.

Ben genelde bu refit interfacelerini proje içerisinde Endpoints diye bir klasör altında toplarım. Burada da öyle yapalım Endpoints diye bir klasör açalım ve içerisine

IRandomUserEndpoint diye bir interface ekleyelim

    [Headers("Content-Type : application/json")]
    public interface IRandomUserEndpoint
    {
        [Get("/")]
        Task GetUserList(int results, int page);
    }

Interfaceimizin içeriği yukarıdaki gibi olacak. Refit ile ilgili postlarımda refit kullanarak e şekilde requestleri nasıl metod haline dönüştürdüğümüzün detaylarına girmiştim. Burada özetle;

  • her bir request e “Content-Type : application/json” header ı ekliyoruz.
  • Get metodu ile direk olarak verilen base url gittiğimizi Get metodunu kullanarak söylüyoruz.
  • results ve page parametreleri de url de karşılıkları olmadığı için otomatik olarak querystring olarak request url ine eklenecekler.

Bu şekilde bahsettiğmiz url ile haberleşecek interface ide oluşturmuş olduk. gerisi refit e kalıyor.

Tüm bu yazdıklarımızdan sonra bir özetlemek istersek ne şekilde bunları kullanabiliriz ve neler olmuş olur şöyle bir üzerinden geçelim.

Uygulamada şu kodu yazdığımız anda;

IApiRequest<IRandomUserEndpoint>.UserInitiated

dediğimiz de Refit bizim için yazdığımız interface in yaşayan bir httpclient halini üretecek. Bu HttpClient a verdiğimiz Fusillade nin RateLimitedHttpMessageHandler ve ModernHttpClient ın NativeMessageHandler  ları refit oluşturacağı HttpClient ın HttpMessageHandler ları olarak kullanılacak. Yani Refit e oluşturmasını istediğimiz HttpClient nasıl bir haberleşme yağacağını söylüyoruz, bu esnada hangi handlerları kullanmasını istediğimizi söylüyoruz ve sonrasında ortaya Refit, Fusillade ve ModernHttpClient ın bir arada kullanılmış olduğu güzel bir client objesi elimze geçiyor. Ve bunun üzerinden kullanımımıza devam ediyoruz.

Bu post u burada bırakıyorum. Bir sonraki post umda bu serinin son yazısını yani kullanıma Polly e Akavache yi de nasıl dahil edeceğimize bakarak, tamamlamış olmayı umut ediyorum 🙂

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.