Queued: Architectural Decisions

By on March 30, 2009 12:07 am

Dojo is a very flexible toolkit; it doesn’t dictate how you organize your code or create your widgets. It simply provides tools, and it’s up to you to decide how you want to fit them together. Developing with AIR puts you squarely in the browser-based application model, but aside from that it mostly stays out of your way as well. As part of our series on the Queued development process, I’m going to take a look at the decisions we made and the philosophies we adopted for the project. It should provide some insight into our process.

Deciding on Functionality

Certain requirements were set in stone from the beginning. We had to use AIR’s database. We had to do an offline mode. We had to support a mode where Queued could run in the background. We had to demonstrate a tight Dojo build. We had to support drag and drop for queue re-ordering—stuff like that. Other things were optional; for example, we could demonstrate the Encrypted Local Store (ELS), but it wasn’t a blocker. After that, though, it was pretty wide open, so we had to decide how much to bite off and still be able to meet our deadlines.

To Be, or Not to Be (Multi-User)?

We did a lot of work around the user model. Should we support multiple accounts? That seems like an obvious feature to include, and as web application developers, we work with user authentication all the time. So, we prototyped a model where the application would store separate user credentials in the ELS and use those to authenticate with Netflix. Here are a few screenshots of the designs we went through:

Queued alpha login dialog
Queued alpha login redux

And after the Less Red Redesign that Torrey talked about in a previous post:

Queued alpha login, attempt 3
Queued alpha login, attempt 3 (password entry)

Integrating the Pieces

Quite a few of Queued’s internals were still in the prototype stage at this point, and right about here was where the OAuth code started getting put in place. This presented us with a challenge. As a team, we’re so used to the web application authentication model—where authentication happens on some remote server—that we were automatically falling into that paradigm almost out of habit. It’s just the way our projects tend to work. However, Queued is essentially a desktop application, so that model doesn’t necessarily fit.

We had originally assumed we’d be able to store sets of login credentials in the ELS and use those at application startup to authenticate with the Netflix API and get rolling. However, Netflix uses the OAuth protocol, and OAuth fundamentally doesn’t work that way.

Traditional username/password-based authentication is like showing your ID to get into, say, a bar. You come and go as you please, but if you leave for long enough, be prepared to flash your ID again to get back in. OAuth, on the other hand, is like having your hand stamped when you enter an amusement park: if you leave, the stamp gets you back in. In this case, the stamp is a token you use to sign your API calls, and Queued gets this token after redirecting you to netflix.com so you can authorize the application to access your account. So, Queued never knows your Netflix login credentials at any time.. This is how OAuth is designed, and it has its benefits, but it didn’t really work with our defined interaction/login model.

Decision Time

We had to make a difficult decision: cut multi-user capability for the 1.0 release. We feel this decision made sense, not only because of the deadline constraints, but because by default, AIR provides different databases and ELS for each operating system user, and we were building a desktop application, after all. We decided to follow that model, and since that’s what AIR expects, the entire toolchain would inherently support that mode of operation.

In the final 1.0 release, therefore, you won’t find any login screens, but you will find a button that kicks off the OAuth handshake:

Queued OAuth-based authentication

It was tough to say goodbye to multi-user capability, but in the end, it let us focus on our core requirements, so in retrospect, it was probably the right decision to make under the circumstances. Queued is Open Source, of course, so don’t feel like you have to wait on us if you want that implemented; aside from the necessary UI work, Queued’s internals are mostly set up so they don’t care whether the app is single-user or multi-user. Have at it :-)

That’s one example of the kind of iterative process we use. Let’s talk architecture.

Deciding on Architecture

Getting a new Dojo-based project started can sometimes be daunting, precisely because it doesn’t force a particular structure on you. The field is wide open. Our first task was to start putting dAIR-based tests together to figure out how we should handle things like window initialization and database access. While that was happening, we had to figure out how to organize our assets and divide up responsibility within the team. We landed on a few key things—nothing really new, but certainly, choices that we could have easily taken in a different direction.

Do as much as possible in CSS.

Dojo’s widget toolkit, Dijit, is robust. You get i18n & a11y out of the box, for example. However, those were outside the scope of this project, which was to be a small fast technology demo. One specific requirement was to keep the built .air package as small as possible, so we made the choice to cut everything that didn’t directly contribute to our project requirements. Since we were dealing with a single WebKit-based engine and didn’t need all of the great cross-engine compatibility stuff in Dijit, and since CSS is generally faster than JavaScript for just about anything that CSS can do, our simple rule was that if you can do something in CSS, you do it in CSS. Even if it took JavaScript code to trigger a change, we pushed as much of the change as possible into the stylesheet. This means, for example, that we created a lot of CSS classes to define state (stuff like “.movie.inQueue”) rather than changing node styles individually.

If you read through the code in the repository, you’ll certainly find some Dijit, but it’s mostly restricted to the initial interface layout (BorderContainer and friends).

Stick with Unobtrusive JavaScript.

Most people advocate the concept of Unobtrusive JavaScript, for a variety of reasons. For Queued, we didn’t have to worry about SEO, making sure our tags were semantic, or any of the big philosophical ideas of Correctness. In an app like this, HTML simply acts as a UI spec. What it came down to for us was separation of design and behavior. It allowed our CSS pros to work their magic on as sparse a DOM as we could provide, without distraction by things like inline event handlers. There was probably a tiny performance benefit to not including the Dojo parser and working through the DOM looking for dojoType attributes and instantiating widgets in markup, but that’s just a nice side effect.

Initializing widgets in this style usually means running some code with dojo.addOnLoad, selecting nodes with dojo.query or dojo.byId, and attaching event handlers to them with dojo.connect. The dojo.behavior module is a great way to do this en masse. We ended up structuring our application code where most modules followed a pattern like this:

dojo.provide("qd.app.someModule");

dojo.require("dojo.behavior");
dojo.require("qd.app");

qd.app.someModule = new (function(){
	// private variables
	var foo = null,
	    bar = null,
	    thinger = null;

	function privateMethod(){
		// code here
	}

	this.publicMethod = function(){
		// code here
	};

	// this module's behaviors
	dojo.behavior.add({
		".someSelector .someOtherOne":{
			onmouseover:function(e){
				// code here
			},
			onclick:function(e){
				// code here
			}
		},
		"#oneParticularNode .childNode img.thinger":{
			onclick:function(e){
				// code here
			}
		}
	});
})();

To use this code in the application, we simply do dojo.require("qd.app.someModule") in the initialization code; the closure gets put together immediately, and the module gets itself all set up.

Being closure-bound like this, each module now had “private” and “public” members, and we could use dojo.behavior in each module to provide that module’s behaviors. Used this way, dojo.behavior will do a bunch of dojo.query/dojo.connect sequences for us; the syntax is more direct than doing them all manually, so it makes for more readable code.

JSLint doesn’t particularly like the someModule = new (function(){ ... })() idiom, however. It always complains, “weird construction. Delete ‘new'”, but it’s a perfectly valid way to create singleton modules, so we stuck with it.

As an example, you may want to look at tooltip.js, which is the module that creates the movie hover info toolip. It’s fully self-contained, without a single public member. Also, try ratings.js, which builds the star rating widgets. Those are good examples of the code structure I’m talking about.

Abstract functionality into internal services.

Finally, even though there isn’t necessarily the concept of a server-side and a client-side in this kind of application, we decided to organize certain things as though there was, by creating various “services” to act as data sources to the application. They’re the middle layer between the application and AIR system-level stuff like the database or ELS or external data like the Netflix API. For example, we created a service to parse Netflix’s XML, a service to handle authentication, and a few others. To the rest of the application, those act as an internal API that abstracts the lower-level details away and keeps the app code focused on the interaction of our objects.

The biggest place where this structure helped was in creating the offline mode. One of our modules continually monitors network availability and stores the status in qd.services.network.available. The rest of the application will automatically toggle between online and offline modes because of this simple bit of code:

qd.__defineGetter__("service", function(){
	//	summary:
	//		Return the proper service (qd.services.online, qd.services.offline)
	//		based on the current network status.
	var b = qd.services.network.available;
	return b ? qd.services.online : qd.services.offline;	//	Object
});

That’s it! This getter transparently (to the caller) switches between the Netflix API and the local database cache as network availability varies, so calling qd.service.titles.find(...), for example, will search for titles using the best available source automatically. The calling code doesn’t need to figure that out for itself.

Check it Out

Queued lives in SitePen Labs, so check that out if you haven’t already. The latest code is hosted at Google Code. Check back soon for more in our Queued series.