Java / JavaEE Architect working in software development (architecture, design and coding) since July 2000, currently aspiring to establish herself as an independent consultant architect in keeping with changing responsibilities and age. Worked in companies like Wipro, Oracle, Siemens, IBM and Sapient. Archanaa has posted 3 posts at DZone. You can read more from them at their website. View Full User Profile

An Inversion of Control Container in Dojo Javascript Framework

06.20.2012
| 5587 views |
  • submit to reddit

Introduction

The software community is no stranger to the concept of inversion of control and dependency injection particularly after the popularity of the Spring application framework which also inspired and encouraged change of the JavaEE specifications to support Contexts and Dependency Injection (CDI) and light-weight EJB3.0. 

However, Dependency Injection (DI) and Inversion of Control (IoC) containers as a concept are not the sole proprietorship of Enterprise Application Frameworks like Spring and do not necessarily require sophisticated framework or containers and unmanageable XML files. In fact, with loosely typed languages like JavaScript, which obviate the need of reflection mechanisms and constructors and encourage duck-typing, it is well within the reach of JavaScript application developers to make a simple and powerful dependency injection container which can run in and respect the limited memory constraints of even a mobile application!

Considering that JavaScript as a language is now finding its use outside browser engines on the server side atop Node.js and Rhino and as a platform-independent mobile application development language, reaching out to the Native mobile platforms with Phonegap plugins, it can safely be said that -JavaScript applications are all set to attain higher levels of complexity. As a result, the good-old design and architecture patterns have started to make as much sense to object-oriented JavaScript developers as they have traditionally made to Java, C++ or .NET application developers.

There are already open source DI frameworks for JavaScript available, such as Wire.js and Squirrel IoC. This article is not aimed at re-inventing the wheel, rather, it is to demonstrate the design under the hood that makes such dependency injection possible using a Dojo code sample and to popularize the concept amongst the breed of JavaScript enthusiasts.

IoC/DI primer and concept of a container

Spring or other Dependency Injection framework evangelists need no introduction to the concept of Inversion Control and Dependency Injection. Those who are more interested in the topic can also read the nearly edifying chapter 'Lightweight Containers and Inversion of Control' in the seminal book - 'J2EE development without EJB' written by one of the creators of Spring Application Framework - Rod Johnson. 

In a modest and abbreviated attempt to give a primer, the following are to be noted -

 

  • A container is one that provides a framework and environment for application objects to be managed and run.
  • Lifecycle Management - A container is a readymade factory that can abstract or hide the creation of application objects obviating the need for application developers to create their own Factories and Singletons using GoF creational patterns. All one needs to do is to specify the class or type of objects or components in a configuration file which is fed to the container and poof- all the desired objects are created and their lifecycle (creation, start, suspend, resume, stop, destruction) is managed by the framework. Containers can callback these lifecycle methods giving an opportunity for application developers to start up or shut down applications gracefully. Additionally, instead of objects or components, one can also plug-in one's own factories to the containers so that those objects produced by the custom factories can also be managed and wired together. 
  • Lookup and Wiring of dependencies - A container enables dependency resolution of application objects or components that are created or managed by it. This means that if object A needs to use object B, then this wiring can be resolved by the container by providing instance A with an instance of B - either during construction itself or by using setters. Furthermore, in case of Application Servers, where use of database connection pools or connections to messaging engines are required, these resources have to be looked up using non-trivial code by applications. A lightweight container such as Spring eases the lookup and enables dependency injection of these resources provided by the Application Server.
  • Configuration - A container enables a consistent way of configuring components or objects either by declaring the configuration values in-line with the application objects or components in a configuration file or further externalizing just the configuration in a separate file other than the application object/component declarations.
  • Programming to interfaces and Testability - A rather interesting side-effect is that actual implementations can be replaced by mock object implementations to enable testing outside of an integrated environment without going through the hassle of ensuring that all dependencies such as connection pool, database, etc are available. This is because DI / IoC encourages the practice of programming to interfaces or contracts so that it is possible to easily swap implementations depending on whether one is doing a unit testing or integration testing or actual production deployment.

How do these concepts profit JavaScript development for browsers or Mobile Web? Bootstrapping an application at a minimum requires components or objects to be sewn together and even simple model-view-controller bootstrapping for most JavaScript RIA applications could be daunting.

When using HTML5/ JavaScript for platform independent Mobile Web application development and reaching out to native platforms using Phonegap, the application quickly reaches a level of complexity that transcends simple model-view-controller wiring. This scenario will be explored in the next section.

The scenario and need for DI container

This section examines a scenario or use case which inspired the requirement of the DI container which was built using Dojo Javascript framework.

 

  • The application under consideration was a mobile application for field service personnel which was to be built in a platform-independent manner using HTML5 and JavaScript.
  • The mobile application was to be usable in offline scenarios which necessitated the storage of application data in the device. The 5MB limit and HTML5 LocalStorage were insufficient for this need and it was required to use Phonegap API for SQLLite.
  • There were use cases that required reaching out to the Native device platform using Phonegap 

 

This presented some challenges for development and testing. As the bulk of the application had to be developed using HTML5 and JavaScript, it made sense to use Chrome or Firefox browsers on the desktop which provided mature debugger environment to step through the application code. However, it would not be possible to run those parts of the application which depended on native phone platform via Phonegap. Testing on phone simulators, on the other hand, even with sophisticated tools like Weinre, does not support step-by-step debugging of JavaScript code.

In order to tide over this limitation, it made sense to first develop and unit test the platform-independent JavaScript portions using mock object implementations on the desktop browsers and then switch to the actual Phonegap plug-in dependent implementations when testing on phone simulators. This could be quite easily achieved by using a DI container.

In order to tide over this limitation, it made sense to first develop and unit test the platform-independent JavaScript portions using mock object implementations on the desktop browsers and then switch to the actual Phonegap plug-in dependent implementations when testing on phone simulators. This could be quite easily achieved by using a DI container.

A DI container would also accord a context rather than the global namespace where objects could be registered with appropriate IDs.

Building the dependency injection container with Dojo

As an example, here is a very small portion of the class diagram of this mobile application which concerns transportation over network. The 'classes' were Dojo JavaScript classes. Though the programming language was JavaScript with Dojo Framework, the principles used in the design were adhering to advocated object-oriented tenets, principles and patterns like abstraction, programming to interfaces rather than implementation, favoring delegation to inheritance, strategy design pattern, facade design pattern etc.

NetworkCommunicationClassDiagram

The following table gives a quick description of the classes and their functions

CLASS DESCRIPTION 
NetworkCommunicationFacade class used by rest of application packages to do network communication with the backend server. Delegates to one of the ServerTransport implementations to actually do the network communication. When it initializes with the init callback method, it registers as a listener for some events and for this, it makes use of EventMulticaster. In order to carry out network interaction with the backend server asynchronously, it makes use of ActionScheduler which helps it prepare requests and send messages to the server. 
ServerTransport

Interface or abstract class to do actual communication with the server over a particular transport and using a particular protocol and message format. This could have several implementations, like doing AJAX communication with JSON content or delegating to the native Android layer for any other binary communication using a different transport.

Accordingly the actual implementations could be AJAXServerTransport or any other transport. 

ActionSchedulerInterface or abstract class to schedule asynchronous operations such as communication with the backend. It was a conscious decision to use this as a thin wrapper over JavaScript's setTimeout or clearTimeout functions.   
JSTimerScheduler Implementation of ActionScheduler which would use JavaScript's setTimeout or clearTimeout.
EventMulticaster Interface or abstract class which would be used to publish, subscribe and unsubscribe with respect to application events. It was a conscious decision to use this as a thin wrapper over Dojo's publish and subscribe API as it could be required to reach out to the native mobile platform as well.

The above classes could be declared by an Application using them in a textual configuration file such as the JSON file given below. Following Spring parlance, these declarations were called 'beans'.  

 

{
	"beans" : {
		"eventConstants" : {
			"type" : "mfwk.utils.EventConstants"
		}
		,"actionScheduler" : {
			"type" : "mfwk.utils.JSTimerScheduler",
		}
		,"eventMulticaster" : {
			"type" : "mfwk.utils.EventMulticaster"
		}
		,"serverTransport" : {
			"type" : "mfwk.network.ServerTransport"
		}
		
		,"networkCommunication" : {
			"type" : "mfwk.network.NetworkCommunication",
			"params" :
			{
				"serverTransport" : "ref : serverTransport",
				"actionScheduler" : "ref : actionScheduler",
				"eventConstants" : "ref : eventConstants"
			},
			"init-method" : "init"
		}
	}
}

 

In the above configuration file, notice that the 'beans' are declared with a name which becomes the identity with which they are registered in a context. The context would be a simple JavaScript object where beans could be added as attributes. The 'type' gives the Dojo fully qualified class name which would be instantiated. The 'params' attribute of the bean declaration is where the class's field variables can be defined as a JSON object. The key of each attribute is the class's field name. The value of each attribute could be a String, Number, Boolean or any JSON Object or Array. However, often it is required to refer to instances of other 'beans' that may have been declared. This is accomplished by giving the value as a String in the form "ref : referencedBeanId". In the above example, the NetworkCommunication instance would be injected with the instances with ids - serverTransport, actionScheduler and eventConstants.

The 'init-method' would be the lifecycle callback method which would be called after the beans have been instantiated and wired up. Similar lifecycle methods could be declared for destruction, pause and resume depending on the state of the environment or the application.

The above application wiring can be brought to life using the Dojo classes BeanLifecycleController and Context. The BeanLifecycleController is the class that is given the JSON configuration containing the above bean configuration to do the following operations on startup - 

 

  • createBeansAndRegisterConfig(/*object*/config, /*string?*/contextName) - This method uses the bean configuration given to instantiate the Dojo classes as per the type given and register them in a context object with the name given for the bean. It also saves the configuration in the context temporarily so that it can refer to the same while wiring the bean's parameters and calling its lifecycle init method. The contextName is optional, a default name can be used for the context. The context would be globally available, that is it can be accessed via the top-level object which is window in the case of browsers.
  • wireParams(/*string?*/contextName) - This method injects the parameters for each bean that has been instantiated and registered in the context. While doing so, it resolves references to other beans in the context by their id/name and injects the same along with the other simple values. To resolve references, the BeanLifecycleController checks the presence of 'ref : ' in the value string using regular expression. References can be arbitrarily nested within objects or arrays in the 'param' declaration.
  • initBeans(/*string?*/contextName) - When all the beans have been instantiated and wired together, this method can be invoked to in turn callback the init methods of each bean, if any. This is the point where the instances can do operations like subscribing for event notifications in the eventMulticaster. The init method of each bean would be invoked with the reference of the context so that, if required, the context could be looked up for other beans.
  • unregisterConfig(/*string?*/contextName) - This method can be called to remove the configuration which had been temporarily saved in the context during the start phase. This would serve the purpose of freeing up memory as the configuration would no longer be required. 

 

The Context class is simple - it saves or 'registers' beans by making them instances with the field name as specified for each bean declaration. Finally, the following code in index.html shows how the application can be assembled and initialized using the BeanLifecycleController -  
<script type="text/javascript"  djConfig="baseUrl:'./'">
	dojo.registerModulePath("mfwk", "../../../mfwk");
	console.log("post dojo");
	dojo.registerModulePath("tests","../../../tests");
	require(["mfwk/base","dojo/text!tests/mfwkconfig.json"], function(mfwkbase, config) {
		var configJson = eval("(" + config + ")");
		var contextName = "test.app.context";
		mfwk.app.Context.setGlobalAppContextName(contextName);
		mfwk.app.Context.getGlobalAppContext();
		//console.log('mfwk.app.Context.contextName' + mfwk.app.Context.contextName);
		//console.log('context '+context);
		var lifecycle = new mfwk.app.BeanLifecycleController();
		lifecycle.createBeansAndRegisterConfig(configJson,contextName);
		lifecycle.wireParams(contextName);
		lifecycle.initBeans(contextName);
		lifecycle.unregisterConfig(contextName);
	});
</script>
 Notice the use of dojo/text!tests/mfwkconfig.json which automatically loads the JSON file and reads its contents into a String. These contents are converted to a JSON JavaScript object using eval and passed to the BeanLifecycleController.

 
The entire source code is attached with this article (WebContent.zip). It consists of the mfwk and the tests directory. The dojo release used was dojo1.7.2.

 DirectoryStructure  

The tests directory contains the index.html and the mfwkconfig.json files. When the project is deployed and the index.html is loaded, a blank grey screen will be seen. However, if the firebug debugger or the Chrome debugger is accessed and the contents of the window object are checked, it would be found that the application has been properly wired, as shown below-

 FirebugScreenshotConclusion

The IoC/DI container demonstrated in this article is intended as an example and was sufficient for the mobile application for which it was intended. It was not a full-fledged IoC/DI container and lacked some basic functionality like the ability of making nested / inline bean declarations like Spring does. 

However, it can be noted that it was fairly straightforward to make a simple IoC/DI container with JavaScript owing to the loose typing and dynamic nature of the language. Unlike Java's DI containers, this one did not use reflection nor required complex code for setter or constructor injection, since JavaScript's objects can be extended to add or set parameters dynamically any time.

Thus, with a minimal effort a very nifty container can be made to leverage the magic of IoC/DI.

Two open source IoC/DI frameworks for JavaScript are Wire.js (https://github.com/cujojs/wire) and Squirrel IoC (http://code.google.com/p/squirrel-ioc/) and the concept of IoC and DI with wire.js had been presented in a Javascript conference by the creator of Wire.js (http://briancavalier.com/presentations/wirejs-2011-jsconf/#0).

At the time of this writing, the author has not tried out these frameworks for doing DI. But no doubt it shows that IoC / DI is seriously being considered by the JavaScript community.

For very modest DI needs, the approach outlined in this article may suffice and prove extremely helpful in building complex JavaScript applications.   

 

AttachmentSize
NetworkCommunicationClassDiagram.jpg54 KB
FirebugScreenshot.jpg187.31 KB
DirectoryStructure.jpg22.91 KB
WebContent.zip8.02 KB
Published at DZone with permission of its author, Archanaa Panda.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)