Cover

Architecting WP7 - Part 7 of 10: Data on the Wire(less)

In this (somewhat belated) part 6 of my Architecture for the Windows Phone 7, I want to talk about dealing with data across the wire (or lack of wire I guess). If you’ve missed the past parts of the series, you you can visit them here:

This is at the heart of the idea that the phone is one of those screens in ‘3 screens and the cloud’. The use-cases for using data are varied including:

  • Consuming public data (e.g. displaying Netflix Queue or Amazon Catalog).
  • Consuming private data (e.g. showing your company’s public data).
  • Data Entry on the phone.

When coming from Silverlight or the web, the real challenge is to meet your needs while realizing you’re working with limitations. When you are creating an app for the desktop (e.g. browser, plug-in based or desktop client) you can make assumptions about network bandwidth and machine memory. While most developers won’t admit it, we will often (to help the project get done) just consume the data we need without regard to these limitations.  For the most part on the desktop this works as we often have enough bandwidth and lots of memory.  On the phone this is definitely different.

You have a number of choices for gathering data across the wire(less) but the real job in architecting a solution is to get just enough data from the cloud. The limitations of a 3G (or eventually 4G) connections aside, making smart decisions about what to bring over is crucial. You may think that 3G should be enough to just get the data you want but don’t forget that you need consume that data too.

I recently chagned updated my Training app for the Windows Phone 7 to optimize the experience. I found that over 3G (which is hard to test without a device) that the experience was less then perfect. When I built originally build the app, I just pulled down all the data for my public courses into the application. In doing that the start-up time was pretty awful. To address this, I purposely tuned the experience to make sure that I only loaded the data that I really needed. But what that meant at the time was to only pull down the information on the selected workshop when the user requested it.  In fact, I did some slight-of-hand to load the outline and events of the workshop while the description of the workshop was shown to the user.  For example:

void LoadWorkshopIntoView(int id)
{
  // Get the right workshop
  _workshop = App.CurrentApp.TheVM.Workshops
                                  .Where(w => w.WorkshopId == id)
                                  .FirstOrDefault();

  // Make the Data Bind
  DataContext = _workshop;

  // Only load the data if the data is needed
  if (_workshop != null && _workshop.WorkshopTopics.Count == 0)
  {
    // Load the data asynchronously while the user 
    // reads the description
    App.CurrentApp.TheVM.LoadWorkshopAsync(_workshop);
  }
}

I released the application to the marketplace and the performance was acceptable…but acceptable is not good enough. Upon refactoring the code, I realized that I was loading the entire entity from the server (even though there were a lot of fields I never used). It became clear that if the size of the payload were lower, then the performance could really be better.

This story does not depend on the nature of your data access. In my case I was using OData to consume the data.  My original request looked like this:

var uri = string.Concat("/Events?$filter=Workshop/WorkshopId eq {0}",
  "&$expand=EventLocation,Instructor,TrainingPartner");

var qryUri = string.Format(uri, workshop.WorkshopId);

By requesting the entire Workshop (and the related EventLocation, Instructor and TrainingPartner) I was retrieving a small number of very large objects.  Viewing the requests in Fiddler told me that the size of these objects were pretty good:

HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 20033
Content-Type: application/atom+xml;charset=utf-8
Server: Microsoft-IIS/7.5
DataServiceVersion: 1.0;
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sat, 13 Nov 2010 21:17:38 GMT

By limiting the type of data to only fields I needed I thought I could see some small change.  I decided to use the projection support in OData to only retrieve the objects I needed:

var uri = string.Concat("/Events?$filter=Workshop/WorkshopId eq {0}",
  "&$expand=EventLocation,Instructor,TrainingPartner",
  "&$select=EventId,EventDate,EventLocation/LocationName,",
  "TrainingLanguage,Instructor/Name,TrainingPartner/Name,",
  "TrainingPartner/InformationUrl");

var qryUri = string.Format(uri, workshop.WorkshopId);

Selecting these fields which were the *only* ones I used reduced the size to:

HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 8637
Content-Type: application/atom+xml;charset=utf-8
Server: Microsoft-IIS/7.5
DataServiceVersion: 1.0;
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sat, 13 Nov 2010 21:17:59 GMT

That’s over a 55+% savings! The size difference wasn’t only in bandwidth, but the cost in memory to deserialize the results, storage size in memory and CPU cost should be substantially cheaper too. I did the same thing with similar results to the Events list.  I saw a 55+% savings there too.

While using OData’s projection mechanism ($select) worked for my case, OData isn’t special here. You could have done the same when you’re building a web service or REST service.  The only case where this type of decision isn’t possible is when you’re building an app on top of a service you don’t control.  Since most REST and Web Services don’t have a built-in mechanism to limit the result set, you could proxy through your servers and trim the offending sized entities too.

The important thing to think about is that you’re working with a different kind of platform than you have in the past (when working strictly on the desktop). You have to think about optimization the whole way through.