My blog has been quiet recently, mainly because I’ve been spending a bunch of time putting together an iPhone app which talks to a set of RESTful WCF services hosted in Azure, backing onto SQL Azure for storage. This post is a technical walkthrough of that architecture and some of the learning experiences, but after this it will be back to normal. I have a couple of nice open-source projects which are coming soon, including a log4net appender which writes to Event Tracing for Windows (ETW), and a plug-in caching behavior for WCF services.
Anyway, the iPhone app: “iFormula1 2011”. Background – it’s a Fantasy Formula 1 game, where you put together your own F1 team from the real Formula 1 paddock. Then as the season progresses, your team’s point go up in line with the race results, and youwatch and see how badly you do compared to other players in the main league, and in the private leagues you can set up. If that’s up your street, check out the app site: http://bit.ly/iF1-2011. If not, the rest of this post is technical. </plug>
Technically, there are some pretty standard disconnected rich-client patterns in use, but the mixture of platforms made for some interesting challenges. The majority of the approaches are equally applicable for client solutions exposing system functionality within the Enterprise. Almost all the pain points were around the learning curve for Objective-C and the iOS framework. The Azure offerings for WCF service hosting and SQL storage work superbly, URL rewriting enables real REST leverage, and switching between on-premise dev boxes and the cloud is seamless (albeit slow – publishing to Azure from Visual Studio is a 20-minute cycle, so you need to manage your release plan).
Challenges & Approaches
Reference data updates
The app is bundled with a baseline set of data – all the drivers and team options, but the attributes are not static. After each race, the total number of points for each option needs to be updated. Less frequently, options may change – e.g. Robert Kubica is injured and is replaced with Nick Heidfeld; HRT take forever to sign up their second driver so there’s a “TBC” to replace. These updates apply to all users, so they need to be distributed as efficiently as possible.
REST supports efficient loading, allowing you to leverage the caching of the Internet without any effort other than some response headers. See Udi Dahan’s excellent post: Building Super-Scalable Web Systems with REST. The key is structuring your service such that many clients use the same request URL. You only want to get deltas, so you need to record a “last updated” stamp on the client and send that to the server to get changes since that stamp. A date/time stamp is the obvious choice, but it means clients will all be sending different requests, each with their own timestamp, e.g. x/y/z/lastUpdated/20110318T013002168 so they won’t share a URL and you won’t benefit from HTTP caching. If you use an integer data version instead, then the URL becomes x/y/z/dataVersion/2, so all clients on the same version will use the same request. The tradeoff is the data updates on the server need to increment the version correctly.
Transactional data updates
The ranking for a fantasy F1 team will change whenever anyone signs up a new team, or joins a league, so it’s not as predictable as reference data. The stats also get updated after every race, and the result of that update is specific to an individual user.
As with the reference data load, it’s useful to try and maximise the number of shared URLs. So I could use one method to get the user’s F1 team and nest all their leagues in the response, but that’s not reusable. Instead I have a very slim “TeamSnapshot” service which contains data unique to one user, and a “LeagueSnapshot” service which contains data shared across a league. The client then makes mulitple calls – it becomes chattier (which I see as a benefit, given the potential for connection loss), but a client is more likely to request a URL which has already been requested, by another user in the same league.
Encryption
There is some sensitive data which needs to go across the wire (users select a username and password), so we need to be able to round-trip encrypt and decrypt between client and server.
Given the standardised nature of encryption, this should be a non-issue, but cross-platform that’s not necessarily the case. With iOS and .NET, AES-256 is provided in both frameworks, so it’s a matter of carefully coding the encryptors so all the parameters (padding, key size etc.) match. DotMac has a good starting point – AES interoperability between .NET and iPhone. I started by working this up into simple iPhone and Windows clients, so you can quickly see that ciphers match on both sides – I’ll put that work up on github soon.
Securing the server
Although there’s only an iPhone client so far, the services are built with the potential for multiple client types. But they are RESTful services which expose CRUD operations to the Internet at large, so we need a simple and efficient way of identifying a type of client and knowing that it is valid to process their request.
This is an interesting one. I’ve adopted the API key approach that the social networking sites use – you register your app, they give you a unique key, and you send the key in every request. If you own all the clients, then you own all the keys and it’s a simple matter to append one to all the URLs, and you have the option to kill all instances of a client server-side if you need to. It’s a pretty flimsy option security-wise, but assuming your service provider is securing the infrastructure, and your concern is making sure that expired clients don’t continue to work, this is fine.
Dealing with network issues
Clients could be disconnected, connected on slow or unreliable networks, the service could be down, the user could terminate the client after receiving an update but before the changes are persisted. All these permutations need to be catered for.
This will depend a lot on what you’re doing, but for my domain I’ve mitigated this by making the client deliberately chatty, and making the service responses as small as possible. This way we’re dealing with a large number of small network transfers, rather than a small number of large network transfers. The chunky approach which is typically preferred in enterprise design has greater exposure to losing a connection, and more complex compensation needed for interruptions.
JSON is to be preferred over XML, as you immediately halve the overhead of the response, and the lack of a strong (XSD-style) contract may be beneficial if you have clients which are on different versions – they need only extract the attributes they’re interested in from the response. Attribute names are worth considering, too – in the iF1 app most of the responses consist of a handful of strings and integers. Verbose attribute names can easily mean the JSON overhead is 3x or 4x the size of the actual data you want to transfer, so there’s a balance between readability and transfer size. JSON is native in WCF with the DataContractJsonSerializer, and there’s a tried-and-trusted open source JSON Framework for iOS on Google Code. Both are compliant to the JSON standard, so interop is painless.
Client-specific issues
Two stand out on the iPhone – memory management and the App Store. iOS does not have a garbage collector, so you need to look after memory yourself, freeing up allocations when an object is no longer needed. I’ve taken a couple of different approaches to fixing up memory leaks, and I’m not happy with either of them. From a .NET point of view, you can think of reference-counted memory management in this way:
- consider all iOS objects as IDisposable
- if you create an object using a constructor – [[Type alloc] init], then you own it and need to dispose (“release”) it
- if you don’t dispose it, the memory will not be reclaimed
- if you get the object from any way other than alloc-init (e.g. from a static method on the class), then you don’t own it
- if you dispose an object you don’t own, your app will crash gracelessly.
In the early days when I was picking up Objective-C I kept over-aggressively releasing objects and crashing the app which slowed me up a good deal. Second approach was to not release anything, let all the memory leak, and then at the point of having a release candidate, run it under instrumentation (XCode has the “Leaks” option which does this very well) to find and fix the leaks. The second option meant I could focus on development, but then had to spend the best part of a day fixing leaks. Option 3 – having a good enough understanding of Objective-C to do memory management right first time, is the aim…
On the non-functional side, if you’re putting out a commercial app then the sales channel has to be accounted for, and with the App Store, Apple make the final decision on whether your app goes in. That can impact functionality, as Apple will reject apps that don’t account for connectivity loss, or leak megabytes of memory, so you need to understand those requirements early in the dev cycle.
And everything takes longer than expected, from signing up as a developer to posting your app for review. So leave as much time as you can, or you’ll be cutting in close if there’s a deadline for getting out there.