Presentation resources View the slides for this presentation on SlideShare

Download the PDF

Read my article on Large-Scale JavaScript Application Architecture

Read 'Essential JavaScript Design Patterns For Beginners' if new to patterns

Introduction

Welcome to the resource page for my talk on Scalable JavaScript Design Patterns last presented at Web Directions (South). It's an extended version of the talk I gave at Fronteers and includes more information about a highly-decoupled architecture for building large applications I've been discussing recently. This version of the talk also covered live code demos of 'Aura', a framework based on some of the ideas in the presentation (see lower down for some code examples). Many of the concepts presented build upon previous work by Nicholas Zakas.





I'm a firm believer in lowering the barrier of entry to (sometimes) complex concepts in JavaScript; this is one reason why the theme of my slides is 'Star Wars' and some of the initial concepts behind the design patterns used are explained through Star Wars metaphors. If you'd rather directly review the material without going through the slides, you can click here to go to my more in-depth article on the topic. I've also written extended notes from the event that may come in useful below.

Notes

In this talk, which expands on my earlier article, I describe a JavaScript application architecture that:

Is highly decoupled , encouraging modules to only publish and subscribe to events of interest rather than directly communicating with each other

, encouraging modules to only publish and subscribe to events of interest rather than directly communicating with each other Supports your application continuing to function even if particular modules fail

Supports improved module management , where a central body is able to manage when modules start, stop or need to be restarted

, where a central body is able to manage when modules start, stop or need to be restarted Supports module-level security , whereby modules are only able to execute behaviour they've been permitted to

, whereby modules are only able to execute behaviour they've been permitted to Supports optional framework agnosisty - this is an opt-in component of the architecture whereby an abstracted API layer is created around specific JavaScript frameworks and toolkits allowing you to easily swap one for another without the need to rewrite any of your code (eg. you can switch your modules from using jQuery to Dojo in just one line).

This is an architecture which has been implemented by a number of different companies in the past, including Yahoo! (for their modularized homepage - Nicholas Zakas has previously discussed this) and is due to be used for some upcoming applications at Aol. Google's GMail also uses some of the concepts presented for their architecture.

The talk covers an introduction to a few key areas of JavaScript development:

Design patterns

JavaScript implementations of patterns, covering: The module pattern in vanilla JavaScript, Dojo, jQuery, YUI, ExtJS, ES Harmony. Modules in AMD and CommonJS

The facade pattern

The mediator pattern

Scalable Application Architecture

Possible problems with most current architectures Thoughts on the requirements for fixing these architectures A proposed JavaScript solution based on the module, facade and mediator patterns



To refresh our minds on how the architecture proposed works, the basic idea is that we combine three well-known design patterns together to form a system where:

Pattern Architectural usage Module Modules are primarily concerned with broadcasting and subscribing to events of interest rather than directly communicating with each other. This is made possible through the Mediator pattern. Facade The Facade is a secure middle-layer that both abstracts an application core (Mediator) and relays messages from the modules back to the Mediator so they don't touch it directly. The Facade also performs the duty of application security guard; it checks event notifications from modules against a configuration to ensure requests from modules are only processed if they are permitted to execute the behaviour passed. Mediator The application core (Mediator) can be implemented in two manners. It can either: Support hot-swapping libraries, frameworks and toolkits without the need to rewrite modules. It does this by providing an abstraction layer around specific JavaScript libraries or frameworks (one Mediator abstraction per library) where the role of the Mediator is to provide a consistant API wrapper around each framework you would like your application to support. You can then easily make a one-line change should you wish to swap out say, jQuery for Dojo or any DOM-manipulation/JS library for another.

Support just one framework/library. The mediator can be implemented such that it only directly calls one single framework and this makes sense if you strongly feel you won't be changing this in the future. I've also seen implementations where the Mediator and Facade are combined, should you also feel you don't want to implement the security layer.

Aura Code Samples

Developers have been requesting some sample code from the Aura framework that I've been building based on the concepts in this talk. Whilst the swappable mediator components and facade are still in a stage of being highly refactored, I can share some of the signatures currently supported as well as the concepts around modules that were demonstrated live at Web Directions.

Note: although a lot of the syntax seen in the examples below resembles jQuery, this is actually all code implemented with the swappable application core as part of the abstraction. This will work with jQuery if the jQuery mediator wrapper is used or with Dojo if the Dojo mediator wrapper is used (without having to rewrite modules to use either specifically).

Some of the methods supported by the Facade:

f.query( context, selector ) - query within a specific context

- query within a specific context f.find( selector ) - queries within a module's own context

- queries within a module's own context f.publish( eventName ) - eg. f.publish({ type: 'eventName', data: { key:value, ..});

- eg. f.publish({ type: 'eventName', data: { key:value, ..}); f.subscribe( eventName ) - eg. f.subscribe({ 'eventName' : callback});

- eg. f.subscribe({ 'eventName' : callback}); f.bind( element, type, function ) - eg. f.bind('button', 'click', this.someMethod);

- eg. f.bind('button', 'click', this.someMethod); f.unbind( element, type, function ) - eg. f.bind('button', 'click', this.someMethod);

- eg. f.bind('button', 'click', this.someMethod); f.css( element, props ) - e.g f.css('button', {'color':'blue'});

- e.g f.css('button', {'color':'blue'}); f.animate( element, props, duration) , f.animate( element, props, duration, callback)

, f.attr( element, attribute ) , f.attr( element, attribute, value )

, f.ajax( ajaxProps ) - eg. f.ajax({url:'index.html', type:'get', dataType:'text', success:function(data){}, error:function(data){}})

Some of the methods supported by the Mediator/Application core:

aura.core.start( module_id ) - start a specific module

- start a specific module aura.core.stop( module_id ) - stop a specific module

- stop a specific module aura.core.startAll() - start all modules

- start all modules aura.core.stopAll() - stop all modules

- stop all modules aura.core.events - event namespace for pub/sub, .bind(), .trigger() etc.

- event namespace for pub/sub, .bind(), .trigger() etc. aura.core.utils - event namespace for module extension, reliable 'type' checking etc.

- event namespace for module extension, reliable 'type' checking etc. sample: aura.core.utils.extend( module, extension )

sample: aura.core.utils.typeOf( object/array/function/string/number ) - behind the scenes uses Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1] for accurate type detection.

The below are two modules from an Aura Todo application.

todo-entry.js

// Module for handling the detection and publication of Todo user-input // The idea here is that if other modules in the system wish to then // consume this data, add it to a list etc. this specific module doesn't // need to be concerned with actions other than event publication. // The first line represents the module definition syntax // The signature for this is: // aura.core.define(module_container, module_factory(facade)); aura.core.define('#todo-entry', function (f) { var input, button = null; return { // Initialization function called when the module is // started by the mediator/application core. Modules // are automatically executed upon load. init: function () { // The facade (f) (amongst other things) provides // secure abstracted access to DOM query and manipulation // methods .find() is local to the module_container // .query() is a more general query method supporting // jQuery/querySelectorAll like querying with or without // a context..css () supports styling etc. // These map through the mediator back to specific library/toolkit // methods for performing the actual actions behind the scenes input = f.find('input'), button = f.find('button'); // Bind click events to an 'add entry' button f.bind(button, 'click', this.handleEntry); // Bind keydown events to the 'todo entry' input text field f.bind(input, 'keydown', this.handleKey); }, // All modules have both an initialization routine // and a method for destroying them. Andrew Burgess has // previously looked at a similar init/destroy pattern for // this architecture which may be maintained for Aura. destroy: function () { f.unbind(button, "click", this.handleEntry); f.unbind(input, "keydown", this.handleKey); input = button = reset = null; }, handleEntry: function () { // Publish an event notification to the rest of the application // passing through the necessary data for other modules to either // look-up the new entry or perform further manipulation to it. // One could technically pass a cached reference to the DOM element // containing the new entry, but there may be performance implications // of using Pub/Sub in that manner. f.publish({ type: 'new-entry', data: { value: input.value, id: f.newGUID() } }); }, handleKey: function (e) { if (e.which == 13) { // Similar to handleKey, we can publish events when something // interesting happens in the application. Ideally, you would // avoid any repetition and maintain DRY coding concepts, but // for the purposes of demonstration this is fine. f.publish({ type: 'new-entry-', data: { value: input.value, id: f.newGUID() } }); } } } });

todo-counter.js

// Simple module for handling counting how many Todo entries // have been added to the application so far. aura.core.define('#todo-counter', function(f){ var counter = null, currentCount =0; return { init:function(){ // reference to the element we will be populating // with the updated counter value counter = f.find('#count'); // subscribe to the new-entry event notification // being published by the last module f.subscribe({ 'new-entry' : this.updateCounter }) }, destroy:function(){ counter = null; currentCount = 0; }, updateCounter:function(){ // update the counter value (ideally we would be taking // into account deleted entries, but I want to keep this // simple). currentCount++; // set the inner HTML of the passed node, DOM element // or jQuery object to the updated counter value f.html(counter,currentCount); // when the counter is updated, change the color // of the text and background to a randomColor (a method // included with the demo app) f.css(counter, {'color':randomColor(), 'background':randomColor()}); // alternatively, Aura also supports chaining as follows // f.css(counter, {'color':randomColor()}).css({'background':randomColor}); f.animate(counter, {'line-height':'100'}, 500); } } });

Developer/Audience Questions and Answers

Q: I liked some of the patterns in the talk, but would prefer to stick with a specific library/toolkit (eg. jQuery) rather than implementing the swappable framework abstractions of the architecture. Is that possible?.

Definitely. The architecture presented is extremely flexible and you don't have to implement it exactly the way it's been presented. Use what works best for your application.If there are particular aspects of it you would like to use, feel free to borrow just those specific parts as each part on it's own is also quite powerful.

For example, implementing the Mediator pattern in your applications provides you a clean approach to having your application's modules using centralized Pub/Sub - this is one easy way of enabling your modules to move away from speaking to each other directly.

The Facade pattern provides a way to easily abstract the complexity of your module or application code by providing a limited API which users can then consume - quite a few people who have attended this talk have liked the idea of abstracting away from public API so you're left with something more convenient to expose and control.

Modules and the module pattern are of course ubiquitous, but the slides should give you an example of how to implement in with most popular frameworks out there at the moment.

Q: How would you go about testing a module that heavily relies on Pub/Sub or the mediator pattern, such as those described in the talk?

Good question. You first want to unit test directly accessing and using the module methods to ensure those work as expected. Next, I would recommend setting up a mediator instance which fires off publish or subscribe calls with the event notification names required (eg. 'new-entry') then testing your assertions based on the ouput returned. This should cover both your cases on both the Pub/Sub and direct-access testing fronts. Have a look at Sinon.js as it has some quite interesting examples of how you can approach testing applications relying on Pub/Sub for communication.

Q: I was considering implementing this architecture using AMD as a base for the modules. I liked the examples I saw in your slides, but I'd like to learn more about AMD and CommonJS. Do you have any recommendations or posts on this I can look at?.

I recently wrote about AMD and CommonJS module formats in a post for Smashing Magazine called Essential jQuery Plugin Patterns. Beyond the jQuery-specific points of the article, there are some good introductions to why we have both completing standards, how they work and how to effectively use them for defining modules. Brian Cavalier also recently gave a useful presentation on AMD/CJS modules that you might be interested in.

Q: Where does an architecture like this fit in with an MVC framework like Backbone.js or SproutCore?

I spoke with Jeremey Ashkenas about this and the feeling was that whilst modules have their place in a Backbone based architecture, you need to be careful when considering abstractions, particularly if you extend this beyond just jQuery/Dojo/YUI etc.

For example, if you were to apply framework abstraction to Backbone so that you can easily switch it out later with say, SproutCore, you really need to justify whether a) there's a genuine likihood of you making such a switch (remember that although both solutions provide 'MVC' on the client-side, they're quite different) and b) whether it's worth the effort at a structural level. Abstracting DOM-manipulation, effects, XHR, script loading etc can work fine, however creating a wrapper around Backbone (due to the internal differences between most frameworks) could easily end up with a sizeable piece of code that's really just there to support potential future changes (that are again, unlikely).

There's a much greater chance of you switching from jQuery to Dojo which is why it makes more sense at that level.

Q: Can this architecture be applied to the server-side/backend?

To be honest, when I initially looked at this architecture I was really just focusing on how it could be applied to front-end applications based on a JavaScript stack. I have however had several discussions with PHP and Ruby developers who feel the decoupled nature of this architecture paired with framework agnostic development principles is a strong concept that could easily be applied to a large range of other programming languages. Whilst my own work in this area is probably going to continue focusing on JS, I'd be happy to hear from anyone that attempts to implement some of these ideas on the backend.