Xamarin.Forms ile Katmanlı Mimariye Giriş – 4

Merhaba

Serimizin bu yazısında servis haberleşmelerini nasıl yapmamız gerektiğini anlatacağım. Normalde HttpClient instance alarak ihtiyacımız olan Get, Post, Put, Delete gibi methodları kullanıyoruz. Fakat bir mobil uygulamada servis haberleşmesi işlemlerinde düşünmemiz gereken sadece verileri client ile server arasında taşımak olmamalı. Verilerin güvenliği, api hata yönetimi, eğer hata alındıysa bunların yönetimi gibi konular da çok önemli. Sonuçta attığımız isteğin takibini yapmamız gerekiyor 🙂

Bu durumlar düşünüldüğünde bize yardımcı olacak arkadaşlarla tanışmamız ve kurallarını öğrenmemiz gerekiyor.

Polly

Kendi tanımını direkt buraya bırakıyorum.
Polly is a library that allows developers to express resilience and transient fault handling policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Kısacası Polly, retry, circuit breaker, timeout, bulkhead isolation gibi konuları tane tane yönetmemizi sağlayan bir araç.

Refit

Refit is The automatic type-safe REST library for .NET Core, Xamarin and .NET

RestService işlemlerimizi Refit üzerinden yapacağız.

ModernHttpClient

Xamarin uygulamalarımızdaki platformlar için optimum network seçimi kullanımı için ModernHttpClient kullanacağız.

Fusillade

Request limit, priority gibi ihtiyaçlarımız için Fusillade kullanacağız.

Daha önce bu arkadaşlarla tanışmadıysanız lütfen önce dokümanlarını okuyun. Amacım bu arkadaşların tüm özelliklerini tanıtmak değil, Xamarin projelerimizde nasıl kullanmamız gerektiğini göstermek.

Servis işlemleri yapacağız ama tüketeceğimiz servisler nerede?

Bu ihtiyacımız için web servis yazma işlemlerine girmeyeceğim. Bunun için Jsonplaceholder sitesinden yararlanacağım.

Voltranımızı oluşturduğumuza göre yapıyı kurmaya başlayabiliriz. Genel geliştirme yaklaşımı olarak birçok projede ihtiyacımız olan işlemleri base projemizde geliştiriyoruz. Bu yapıda ise base projemizde requestlerin geçtiği yapıyı yazacağız.

IApiRequestSelector

    public interface IApiRequestSelector<T> : IServiceBase
    {
        T GetApiRequestByPriority(IApiRequest<T> apiRequest, PriorityType priorityType);
    }

IApiRequest

    public interface IApiRequest<out T>
    {
        T Speculative { get; }
        T UserInitiated { get; }
        T Background { get; }
    }

ApiRequestSelector

    public class ApiRequestSelector<T> : IApiRequestSelector<T>
    {
        public T GetApiRequestByPriority(IApiRequest<T> apiRequest, PriorityType priorityType)
        {
            switch (priorityType)
            {
                case PriorityType.Speculative:
                    return apiRequest.Speculative;
                case PriorityType.UserInitiated:
                    return apiRequest.UserInitiated;
                case PriorityType.Background:
                    return apiRequest.Background;
                default:
                    return apiRequest.UserInitiated;
            }
        }
    }

ApiRequest


    public class ApiRequest<T> : IApiRequest<T>
    {
        private readonly Lazy<T> _background;
        private readonly Lazy<T> _userInitiated;
        private readonly Lazy<T> _speculative;
        private string _baseApiAddress = GlobalSetting.Instance.BaseEndpoint;

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

        public T Background => _background.Value;
        public T Speculative => _speculative.Value;
        public T UserInitiated => _userInitiated.Value;

        public string BaseApiAddress
        {
            get { return _baseApiAddress; }
            set { _baseApiAddress = value; }
        }

        public T CreateClient(HttpMessageHandler handler, string baseApiAddress = null)
        {
            HttpClient client;
            if (GlobalSetting.Instance.UseNativeHttpHandler)
                client = new HttpClient(handler);
            else
                client = new HttpClient();
            client.BaseAddress = new Uri(baseApiAddress ?? BaseApiAddress);
            return RestService.For<T>(client);
        }
    }

IApiRequestSelector bize priority esnekliğini ve uygulamada yapacağımız tüm isteklerin geçeceği ortak bir method veriyor. ApiRequest sınıfı ise isteğin hangi priority üzerinden gideceğini, ModernHttpClient özelliğini ve Refit üzerinden rest servisin ayağa kalmasını sağlayacak.

Tüm servis işlemlerimiz için gerekli yapıyı base projemizde kurduğumuza göre artık “x” mobil projesi için olan “y” servisini tüketebiliriz.

Örnek Kullanım

Senaryomuz https://jsonplaceholder.typicode.com/ sitesinden users get işlemi yapmak olacak. Bunun için önce Refit kullanarak request interfaceimizi yazıyoruz.

IUserEndpoint

    [Headers("Content-Type : application/json")]
    public interface IUserEndpoint
    {
        [Get("/users")]
        Task<List<Models.User>> Get();
    }

Bu interfacei kullanarak, VM’ler içinde kullanacağımız User servisini yazacağız.

IUserService

    public interface IUserService : IServiceBase
    {
        Task<List<Models.User>> Get(PriorityType priorityType);
    }

UserService


    public class UserService : IUserService
    {
        private readonly IApiRequest<IUserEndpoint> _request;
        private readonly IApiRequestSelector<IUserEndpoint> _apiRequestSelector;
        public UserService(IApiRequest<IUserEndpoint> request,
            IApiRequestSelector<IUserEndpoint> apiRequestSelector)
        {
            _request = request;
            _apiRequestSelector = apiRequestSelector;
        }
        public async Task<List<Models.User>> Get(PriorityType priorityType)
        {
            List<Models.User> result = null;
            Task<List<Models.User>> _task = null;
            Exception exception = null;

            try
            {
                var _api = _apiRequestSelector.GetApiRequestByPriority(_request, priorityType);
                _task = _api.Get();
                result = await Policy
                          .Handle<ApiException>()
                          .WaitAndRetryAsync(retryCount: 2, sleepDurationProvider: retryAttempt =>
                          TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
                          .ExecuteAsync(async () => await _task);
            }
            catch (ApiException apiException)
            {
                exception = apiException;
            }
            catch (Exception ex)
            {
                exception = ex;
            }

            return result;
        }
    }

Burada onemli olan nokta tamamen “result = await Policy” ile başlayan kısım. Basir bir örnek olması açısından sadece WaitAndRetryAsync methodunu kullandım fakat refit ve polly’nin diğer yeteneklerini de ihtiyaçlarımıza göre buraya dahil edebiliriz.

Serimizin ilk yazısında da belirttiğim gibi gelişime açık, değişime kapalı bir yapı kurduğumuz için bazı sınıflarımızda geliştirmeler yaparak yapımızı genişleteceğiz.

ViewModelLocator > Init

            builder.RegisterGeneric(typeof(ApiRequest<>)).
                As(typeof(IApiRequest<>)).InstancePerDependency();
            builder.RegisterGeneric(typeof(ApiRequestSelector<>)).
               As(typeof(IApiRequestSelector<>)).InstancePerDependency();

MyXF.Client.mobile > App.cs

GlobalSetting.Instance.BaseEndpoint = "https://jsonplaceholder.typicode.com";

ListPage

    <ContentPage.Content>
        <StackLayout>
            <ListView ItemsSource="{Binding Users}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <TextCell Text="{Binding name}"
                                  Detail="{Binding email}"/>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackLayout>
    </ContentPage.Content>

ListPageViewModel


    public class ListPageViewModel : ViewModelBase
    {
        private IList<User> _users;
        public IList<User> Users
        {
            get
            {
                if (_users == null)
                    _users = new ObservableCollection<User>();
                return _users;
            }
            set
            {
                _users = value;
            }
        }

        private readonly IUserService _userService;
        public ListPageViewModel(IUserService userService)
        {
            _userService = userService;
        }
        public override async Task InitializeAsync(object navigationData)
        {
            IsBusy = true;
            var result = await _userService.Get(PriorityType.UserInitiated);
            foreach (var item in result)
                Users.Add(item);
            IsBusy = false;
        }
    }

Sonraki yazılarımızda geliştirdiğimzi voltran’ın daha fazla özelliğini kullanacağız. Sizler de yapıya hakim olmak için kodlar üzerinden debug ile giderseniz süper olur.

Yiğit

Xamarin Developer, Consultant & Architect. Community Leader and Director of Xamarin Türkiye

3 Comments

You can post comments in this post.


  • Hocam merhaba oncelikle cok tesekkurler degerli bilgiler icin. Benim sormak istedigim su : 2 farkli urldeki servise baglanmak istersek nasil bir yol izlemeliyiz. Yani _baseApiAddress = “https://jsonplaceholder.typicode.com” verdik peki ayni projede “https://reqres.in/api/users” bu adrese de ayrica istek atacaksam nasil yol izlemeliyim? simdiden tesekkurler.

    Kadir 2 sene ago Reply


    • Selamlar,
      Öncelikle güzel sorun için teşekkür ederim. Bu isteğini çok ufak dokunuşlar yaparak karşılayabiliriz. Bir sonraki blog yazımda buna değineceğim.

      Yiğit 2 sene ago Reply


    • Kadir merhaba,
      Yeni blog yazımda çözümü okuyabilirsin.

      Yiğit 2 sene ago Reply


Post A Reply