Home .NET Reactive Extensions: client for conditional api with Cache-Aside & Refresh-Ahead strategy

Reactive Extensions: client for conditional api with Cache-Aside & Refresh-Ahead strategy

by admin

Reactive Extensions: client for conditional api with Cache-Aside & Refresh-Ahead strategy

Introduction

In this article, I want to look at developing a client library for a conditional api service.I will use Habrahabra’s imaginary Rest-api as such a service.
To make such a chore a little more interesting, we’ll complicate the requirements by adding caching and spicing it all up with the library Reactive Extensions
Everyone who is interested is invited under the cat.
Let’s imagine we have a url of the format nonexisting-api.habrahabr.ru/v1/karma/user_name , which returns the following json:

{"userName" : "requested user name", "karma" : 123, "lastModified" : "2014-09-01"}

Referring to such a service, deserializing the response, and displaying the results to the user is all fairly trivial.Perhaps a naive implementation could look something like this :

public sealed class NonReactiveHabraClient{private IHttpClientHttpClient { get; set; }public NonReactiveHabraClient(IHttpClient httpClient){HttpClient = httpClient;}public async Task<KarmaModel> GetKarmaForUser(string userName){var karmaResponse = await HttpClient.Get(userName);if (!karmaResponse.IsSuccessful){throw karmaResponse.Exception;}return karmaResponse.Data;}}

Let’s add caching

Working with a mobile app is very different from working with a desktop or web app.The mobile application is used "on the run, " one-handed, often under poor communication conditions.Of course, the user expects the fastest possible display of the information he is interested in.There is an obvious need to cache data.
The key feature of our application is rarely updated data, low criticality of freshness and relevance of data.That is, we can afford to show information from the previous run. Many weather apps, twitter clients and others have a similar feature.
The following logic is quite common in applications like ours :

  1. the application should start quickly;
  2. show cached data;
  3. try to get fresh data from the backend;
  4. if successful, save the data to the cache;
  5. display the new data to the user or report an error.

Or the same, but as a diagram (I hope I haven’t completely forgotten how to draw sequence diagrams).
Reactive Extensions: client for conditional api with Cache-Aside & Refresh-Ahead strategy
There are several main strategies to handle the local cache of an application. I won’t go through them all, in this article we’re interested in the Cache-Aside approach (or pattern).
The main idea of the pattern is that the cache is just a passive data repository. So the task of updating the data falls on the shoulders of the code that uses the cache. For such a cache you can define rather simple interface.

public interface ICache{bool HasCached(string userName);KarmaModel GetCachedItem(string userName);void Put(KarmaModel updatedKarma);}

The cache that implements this interface sufficiently meets requirements 2 and 4 of the previous list. Items 2, 3, and 4 together are some version of the approach called Refresh-Ahead Caching.
I would like to note that the above approach is not a classical implementation of the two patterns, but only a combination of the basic ideas. However, this is what the patterns are for.
Hopefully, the standard iterative implementation of this approach will not be difficult for the reader, so I will go straight to the variant that uses Reactive Extensions. In addition, I expect that the reader is already familiar with Rx, at least on a general introduction level. If you’re not familiar with Rx or want to refresh your memory, I suggest you read the article from SergeyT "Reactive extensions" and asynchronous operations

Implementation

Let’s start by creating a project in Visual Studio and specify the project type as Class Library. We will need the NuGet package Rx-Main:

Install-Package Rx-Main

Define the abstraction over the http-client :

public interface IHttpClient{Task<KarmaResponse> Get(string userName);}public class KarmaResponse{public bool IsSuccessful { get; set; }public KarmaModel Data { get; set; }public Exception Exception { get; set; }}public class KarmaModel{public string UserName { get; set; }public int Karma { get; set; }public DateTime LastModified{ get; set; }}

The specific implementation of http requests, parsing and deserialization of the response, error handling is not important to us.
Define the interface of our api client :

public interface IHabraClient{IObservable<KarmaModel> GetKarmaForUser(string userName);}

The key point here : we return IObservable<T> , that is, a "stream" of events that can be subscribed to.
Finally, let’s define the implementation of our HabraClient:

public sealed class ReactiveHabraClient : IHabraClient{private ICacheCache { get; set; }private IHttpClient HttpClient { get; set; }private ISchedulerScheduler { get; set; }public ReactiveHabraClient(ICache cache, IHttpClient httpClient, IScheduler scheduler){Cache = cache;HttpClient = httpClient;Scheduler = scheduler;}public IObservable<KarmaModel> GetKarmaForUser(string userName){return Observable.Create<KarmaModel> (observer =>Scheduler.Schedule(async () =>{KarmaModel karma = null;if (Cache.HasCached(userName)){karma = Cache.GetCachedItem(userName);observer.OnNext(karma);}var karmaResponse = await HttpClient.Get(userName);if (!karmaResponse.IsSuccessful){observer.OnError(karmaResponse.Exception);return;}var updatedKarma = karmaResponse.Data;Cache.Put(updatedKarma);if (karma == null || updatedKarma.LastModified > karma.LastModified){observer.OnNext(updatedKarma);}observer.OnCompleted();}));}}

The code is quite straightforward: we create and return a new Observable object, which immediately returns the cached data (if any) and then queries the updated values asynchronously. In case the data were updated (the LastModified ) we notify subscribers again, put the data into the cache and finish the sequence.
This way, the code of the View Model using our ReactiveHabraClient will be compact and declarative :

public class MainViewModel{private IHabraClient HabraClient { get; set; }public MainViewModel(IHabraClient habraClient, string userName){HabraClient = habraClient;Initialize(userName);}private void Initialize(string userName){IsLoading = true;HabraClient.GetKarmaForUser(userName).Subscribe(onNext: HandleData, onError: HandleException, onCompleted: () => IsLoading = false);}private void HandleException(Exception exception){ErrorMessage = exception.Message;IsLoading = false;}private void HandleData(KarmaModel data){Karma = data.Karma;}public bool IsLoading { get; set; }public int? Karma { get; set; }public string ErrorMessage { get; set; }}

Of course, the attentive reader has already noticed that there is no implementation here INotifyPropertyChanged and dispatchable ( OnNext , OnError and OnCompleted are not executed in a UI thread). Let’s imagine these tasks are taken over by your favorite MVVM framework.
We could perhaps finish the article at this point but we haven’t covered the testing question at all. Indeed, it is often not very convenient to write unit-tests for asynchronous code. What to say about asynchronous code using Rx?

Testing

Let’s try to write some unit tests for our ReactiveHabraClient and MainViewModel.
To do this, create a new project of Class Library type, add a link to the main project and install some NuGet packages.
Specifically : Rx-Main , Rx-Testing , Nunit and Moq

Install-Package Rx-MainInstall-Package Rx-TestingInstall-Package NUnitInstall-Package Moq

Let’s create a class ReactiveHabraClientTest inherited from ReactiveTest
ReactiveTest – is a base class that comes with the Rx-Testing package. It defines several methods that will be useful for us when writing tests.
I’m not going to clutter the article with large listings, and I will give only one test for each of the classes here. The rest of the tests can be found on GitHub. You can find the link to the repository at the end of the article.
Let’s test the following scenario : With an empty cache HabraClient should download the data, put it in the cache, call OnNext and OnCompleted
To do this, we need Mock-i on IHttpClient , ICache We can also use the class TestScheduler From the Rx-Test package.
TestScheduler implements the interface IScheduler and can be substituted for a platform-dependent implementation of the scheduler. The class allows us to literally manage time and execute asynchronous code in steps. For those interested, I highly recommend the excellent article Testing Rx Queries using Virtual Time Scheduling

[SetUp]public void SetUp(){Model = new KarmaModel {Karma = 10, LastModified = new DateTime(2014, 09, 10, 1, 1, 1, 0), UserName = USER_NAME};Cache = new Mock<ICache> ();Scheduler = new TestScheduler();HttpClient = new Mock<IHttpClient> ();}

And let’s start writing the test itself.

Arrange

Let’s set the behavior of Mock-ups : the cache will be empty, the data will be loaded successfully.

Cache.Setup(c => c.HasCached(It.IsAny<string> ())).Returns(false);HttpClient.Setup(http => http.Get(USER_NAME)).ReturnsAsync(new KarmaResponse{Data = Model, IsSuccessful = true});var client = new ReactiveHabraClient(Cache.Object, HttpClient.Object, Scheduler);

In the case under test, we expect a sequence of one call to OnNextand one call to OnCompleted.
Let’s create this sequence :

var expected = Scheduler.CreateHotObservable(OnNext(2, Model), OnCompleted<KarmaModel> (2));

Explanations are needed here.Method OnNext(2, Model) is a method defined in ReactiveTest
Its signature is as follows :

public static Recorded<Notification<T> > OnNext<T> (long ticks, T value)

It essentially creates a record that the OnNext method with the Model parameter was called.The magic number 2 is the time in "ticks" for our TestScheduler Not a very nice solution, but quite understandable. In the "tick" number zero we create TestScheduler , in "tick" number one we subscribe to events, and in "tick" number two the message sequence must arrive.

Act

var results = Scheduler.Start(() => client.GetKarmaForUser(USER_NAME), 0, 1, 10);

This is where we run TestScheduler which will be created at the zero tick, and will subscribe to the client.GetKarmaForUser(USER_NAME) to the first "tick". The last parameter is the "tick" on which Dispose will be called, but in this case we don’t care about that value.
And finally, the last step.

Assert

ReactiveAssert.AreElementsEqual(expected.Messages, results.Messages);Cache.Verify(cache => cache.Put(Model), Times.Once);

Make sure that the sequence of messages we received is the same as the expected sequence. We also check that the updated model is saved in the cache.
Ideally, this test should have been split into two, but I wanted to demonstrate that the techniques we are used to continue to work in the rx world as well.
Test for MainViewModel will be slightly different.
Let’s create a Mock for IHabraClient and declare KarmaStream like

 

Subject:
[SetUp]public void SetUp(){Client = new Mock<IHabraClient> ();KarmaStream= new Subject<KarmaModel> ();}

Class Subject<T> implements both interfaces IObservable and IObserver We can return KarmaStream from the method GetKarmaForUser and use it to manually call OnNext , OnCompleted and OnError In this case we do not need "magic" c TestScheduler
Consider one of the tests :

[Test]public void KarmaValueSetToPropertyWhenOnNextCalled(){Client.Setup(client => client.GetKarmaForUser(USER_NAME)).Returns(KarmaStream);var viewModel = new MainViewModel(Client.Object, USER_NAME);KarmaStream.OnNext(new KarmaModel {Karma = 10});Assert.AreEqual(10, viewModel.Karma);}

Source code

The full, code for this article can be found at GitHub
Although the article mentions development for Windows Phone, the code in the repository is written for .Net 4.5. I made this step consciously because those who don’t have WP SDK installed couldn’t open and run the project. However, by simply copying the files into the project with the Class Library for WP8 you can get a compilable build. In addition, the Rx also supports some PCL profiles.

Conclusion

A complete and detailed description of all the possibilities and approaches of reactive programming was not the goal of this article. Also, I did not set myself the task of writing and building a ready-made library. This article describes some template which I’ve used for commercial and personal Windows Phone projects.
I will gladly accept reasonable criticism in the comments and comments about mistakes in the personal.

Links

I used the following sources in preparing the article :

You may also like