Monday, April 21, 2014

The outline of a mobile architecture using Xamarin

This post is going to be a little abstract. It's going to sum up about two years of best practices in mobile development using Xamarins products. Please feel free to criticise! :)

The overview

A common misconception about Xamarin (I use the name Xamarin to represent both Xamarin.Ios and Xamarin.Android) is that it's UI cross platform. It is not and that's a good thing. The rationalization for this is that if you use a UI cross platform tool, it is only going to be as good as the common denominator for all platforms you choose to include. This makes for a substandard app/product. You can also find the same reasoning at Xamarins own website.

This leads you to a setup that looks something like this.





In most of my projects I communicate to a JSON enabled REST-backend.

Inversion of control

I got to mention this early. This is a must have. A lot of code in core is dependent on platform specific implementations, such as Storage and Networking. We inject these platform specific bits of code using IoC. Use your favorite flavour. I, however, recommend Autofac since it plays nicely with all mobile platforms.

A note worth making is that we don't include any IoC bits in the core. It's all in the UI layer.

JSON serializer

If you're having a JSON/REST backend you need to serialize and deserialize objects. There are a lot of serializers out there. Personally I use Newtonsoft.json (also called json.net). In fact, I'm so hooked on it that I hardcode it into my networking and storage more than I should.

Everything has an ID

I always assume that each object/resource has an ID and that it is available at a unique URI. After all that is what REST is all about. The ID is really the unique Uri.

For example, a user object might have a database ID of 42. The User object exposed by REST has the unique id of http://www.mysite.com/users/42. When I cache and/or store objects locally I use either a shorthand (/users/42) or the entire URI as a key. There is also an advanced version of this where I store a timestamp and use it for concurrency and caching.

Entities/Models

This is often a PCL that I share between the projects. It doesn't have to be a binary/IL share really. I usually just do that since I'm lazy.

If you feel like creating C# objects from json, try out http://json2csharp.com/. Just paste your JSON and get a C# POCO back.

The core

This is a Portable Class Library (PCL). Try to fit all of your networking, storage and business logic into this library. The folder structure and naming is of course a matter of personal option and can be exchanged for anything you would like.

The boxes on the left aren't changed that often. I've got my base code that I might tweak between projects due to specific requirements. The boxes on the right are the ones that are very project specific.


Storage

Stores stuff locally on the mobile device. Often objects serialized as JSON. I like to keep storage as simple as possible, often just wrapped in an interface like this.

IKeyValueStorage
+ void Set(string key, string value)
+ string Get(string key)

The actual implementation differs on each UI platform.

Based on the IKeyValueStorage I usually create an IObjectStorage that takes care of the serialization of objects.

IObjectStorage
+ void Set(string key, object item)
+ T Get<T>(key);

The implementation would look something like this.

public class ObjectStorage : IObjectStorage
{
  private IKeyValueStorage _storage;
  public ObjectStorage(IKeyValueStorage storage)
  {
     _storage = storage;
   }

   public void Set(string key, object item)
   {
      _storage.Set(key, JsonConvert.SerializeObject(item));
   }

   public T Get<T>(string key)
   {
      return JsonConvert.DeserializeObject<T>(_storage.Get(key));
   }
}

You would need add error handling, logging and all that other stuff of course. The important part here is that we can implement the ObjectStorage in core since the platform specific parts are wrapped inside the implementations of IKeyValueStorage.

There is also one more kind of storage that I usually set up and that is the ISettings storage. It defines all the settings I need for a project. It is very project specific. This example stores a BaseUri that the RestClient needs.

public interface ISettings
{
   public string BaseUri { get; set; }
}

public class Settings : ISettings
{
   private IKeyValueStorage _storage;
   public Settings(IKeyValueStorage storage)
   {
      _storage = storage;
   }

   public string BaseUri
   {
      get { return _storage.Get("BaseUri"); }
      set { _storage.Set("BaseUri", value ); }
   }
}

And then add default handling and stuff like that. Set up the Settings-object in the IoC bootstrapper. Remember to initialize it at first run to ensure that we have default values or add handling of default values.

Networking

I often only have a couple of classes here. The main one being the RestClient along with its interface IRestClient. It's an async enabled wrapper built on top of HttpClient and extended with ModernHttpClient to gain some performance and stability, mostly on Android. I have a separate post about that here.

The rest client is built as generic as possible but do give you an option to use entities through generics.

var obj = _restClient.Get<MyObject>(_theUri);

The RestClient handles both what I call short URIs and complete URIs. The short version needs a base URI that the RestClient gets from the Settings.

To make the RestClient aware of the settings class we simply define it in the constructor as below. This is only a small part of a RestClient.

public class RestClient
{
   public RestClient(HttpMessageHandler handler, ISettings settings)
   {
      ... store local copies
   }

   public async Get<T>(string uri)
   {
      if(!uri.BeginsWith("http"))
      {
          uri = _settings.BaseUri + uri;
      }

      using(var client = new HttpClient(_handler))
      {
          var content = await client.GetStringAsync(uri));
          return JsonConvert.DeserializeObject<T>(content);
      }
   }
}

But wait, what is the HttpMessageHandler? Since we're using the ModernHttpClient we need to provide a platform specific HttpMessageHandler. You just have to set that up in the IoC bootstrapper for each platform to ensure that each platform gets the correct handler. 

Repositories

In my world I use repositores to wrap access to a single set of entites such as users. My repositories also handles caching if needed. The code below is pseudo code to make the point.

public class UserRepository
{
   public UserRepository(IRestClient restClient, IObjectStorage storage)
   {
      // store to local fields
   }

   public User FindById(int id)
   {
      string key = String.Format("Users/{0}", id);
     
      try
      {
         var user = _restClient.Get<User>(key);
         _storage.Set(key, user);
         return user;
      }
      catch(NetworkUnavailableException ex)
      {
         return _storage.Get(key);
      }
   }
}

Services

Sometimes you need to access multiple repositories and other resources. A service is what coordinates that into a single operation. A service can be used to assemble a viewmodel to return to the UI layer.

To sum up

  • Inversion of control is vital to keep a multiplatform project going. Once you wrap your head around it it does really simplify the development process.
  • We didn't touch testing, but with IoC, it's simple... Or at least doable...
  • Single Responsibility Principal is important, but don't go bananas. Sometimes shortcuts really saves a lot of time. I always hardcode the Json.net serializer since I know that I'm not going to exchange it for something else. It's not worth the extra abstractation layer in my book.
  • There is no one-size-fits-all solution

No comments:

Post a Comment