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ıyoruz. Bu 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));
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.