AJS_ODL Motivation

Redundant Retrieval

The loading of resources on-demand is an important concept in non-trivial web -sites and -applications. Visitors to a given site often arrive via a link in a search engine, without knowing what to expect, and then leave within seconds because the site does not fit their requirements. The implications of this are significant because, without careful design and implementation, the user agent will start retrieving its content and supporting-functionality in ignorance of the visitor's imminent departure.

This pattern of activity is redundant and highly inefficient, in that it consumes bandwidth that is wasted should the visitor move on within seconds. It is especially unfavourable if the site provider pays for that bandwidth. Moreover, it may cause a site to give the impression of of being sluggish and unresponsive should the content and functionality take appreciable time to arrive on the client machine.

Contents Redundant Retrieval
Loading on Demand
Clutter
Compounding the Overhead
Using a Loading Manager
The Case for Method-Call Interception

Loading on Demand

A far better approach is to retrieve content and functionality from the server only when it is truly needed. For example, it is far better to retrieve the JavaScript code that implements validation for a complex data-entry form only if the user starts entering data into that form.

Taken to its logical extreme, this approach suggests that one can implement a site such that user agents retrieve only the barest minimum from the server initially – sufficient to give users an adequate impression of a site's content and purpose – after which it loads all other functionality only on-demand. This offers great benefits in terms of efficiency and performance; sites that take the on-demand approach will, initially, arrive faster on the user's machine, and, on average, will be far less costly in terms of bandwidth to deliver because those visitors that arrive and then leave without exploring the site will draw only minimal resources from the server.

However, the traditional approaches to on-demand loading offset these savings because the mechanisms that trigger and perform the loading of resources when they are needed incur significant overheads. This page examines these drawbacks, and shows that using method-call interception (MCI) to implement on-demand loading will deliver all the benefits with minimal overheads.

Clutter

A variety of techniques can be used to retrieve resources on demand, such as XMLHTTPRequest (XHR), or the injection of script, link or image elements into the HTML for a given page. WebSockets also hold considerable promise here, as they enable a system's client-side to maintain a single fast-connection with the server as a delivery pipeline through which a variety of different data-types may be acquired with very low network-overhead.

However, on-demand loading pivots on the concept that execution of a given piece of functionality in a system triggers the download of resources, yet implementing such trigger-mechanisms threatens other design-tenets. A core principle in software development holds that a given 'entity' in a system – a function, for example, or a particular object-type – should do one thing and one thing alone (hopefully, very well). Yet if we use, say, script-element injection to acquire a particular piece of JavaScript, the code that creates and injects the script element must go somewhere in the system.

Example 1 illustrates this. Here the initial statements in someMethod create a script element, sets its src attribute to the URL of the desired code, and then inserts the element into the head element for the page in question. It does not play its central role until after that, meaning that the loading code is disjunctive – it does not relate directly to the rest of the function body, and this serves to clutter the function, thus harming readability and comprehension.


 // -- Example 1 --------------------------------------------------------------

 var SomeObject =
    {
    someMethod : function ()
        {
        var ScriptElem = document.createElement ("script");

        ScriptElem.src = "SomeJavaScript.js";

        document.getElementsByTagName ("head").appendChild (ScriptElem);

        // Only from here-on does someMethod get
        // on with fulfilling its central role.

        }

     };
            

Compounding the Overhead

Sadly, the problems do not stop there. The AJS_Validator Motivation page shows that one solution to the overheads that tradtional approaches to design-by-contract incur is to use conditional comments. This is because, typically, we want our validation framework in place only during development of a given system, and so conditional comments allows us to remove the code that checks arguments, return values, and system state from the deployed version.

This gives the benefits of method-call validation during development, without its performance overhead when the system is out in the wild, but we have no such recourse when using on-demand loading, as the benefit of that technique is realised only when a system is deployed. Moreover, the mechanism that Example 1 demonstrates is simplistic. Without additional logic, each subsequent call to the function will see the loading code execute once again. It would be far better if we could to remove the loading code once it has done its job, thus relieving the application of such dead-weight.

This issue is deeper than it appears initially. We may have a number of functions that all contain identical statements that effect retrieval of the same resource, but as soon as any instance of those statements execute, its siblings at all locations in the system are also rendered redundant. Thus we need a way to disable the loading code in all of these locations en masse.

Furthermore, the loading code is entirely specific; it describes not only what to load, but how to effect the loading too. Ideally, and in keeping with the concept that underlies regular expressions, we would have a way of stating what should be retrieved without providing details of the retrieval process. We may also need to load a mix of resources, where the execution of certain functions in the system trigger the loading of images and styling rules, as well as JavaScript code.

Still further, the loading trigger is 'bound' to the function. In Example 1, someMethod will always load a file called SomeCode.js. This impinges on the reusability of someMethod. If it form parts of a library that we employ in a number of applications, then we are stuck with always loading SomeCode.js, irrespective of the application in which SomeeEthod finds itself operating.

All of these points run contrary to the very aim of loading on-demand, which is to pay only for what one uses.

Using a Loading Manager

Clearly, we can manage the repetition of loading code across a system by implementing some form of 'loading manager', where a single call to that component triggers the loading of perhaps a number of resources - one could pass an array of URLs - and Example 2 illustrates this idea.

This moderates a number of the drawbacks explored above, however, we are still stuck with the non-cohesive call to the loading manager, so the code for the functions concerned still suffers from a degree of clutter. Moreover, once a resource has been retrieved, the system must still execute the calls to the manager, even though they are now redundant. This is ugly and inelegant.


 // -- Example 1 --------------------------------------------------------------

 var loadCode = function ()
    {
    var ScriptElem = null;
    var HeadElem   = document.getElementsByTagName ("head");

    var LoadedURLs = [];

    return function (URL)
       {
       if (URL in LoadedURLs) { return; }

       ScriptElem      = document.createElement ("script");

       ScriptElem.src  = URL;

       HeadElem.appendChild (ScriptElem);

       LoadedURLs[URL] = true;

       }

    } ();

 // ------------------------------------

 var SomeObject =
    {
    someMethod : function ()
        {
        loadCode ("SomeJavaScript.js");            // Better, but still disjunctive and redundant
                                                   // following its first invocation.
        //
        // Remainder of someMethod from here-on...
        //

        }

     };
            

The Case for Method-Call Interception

The points made above are the motivation for AJS_ODL (AspectJS On-Demand Loading), which provides the benefits of loading on-demand with only minimal overhead. We can create a special kind of object that describes what should be loaded, the method by which that resource should be retrieved, and the names of the methods that should trigger the loading of the resource(s) indicated. We can then pass that object to AJS_ODL, which will attach a prefix function to the methods in question, such that subsequent calls to those methods cause the prefix to initiate retrieval of the required resources.

This absolves us from having to implement the various loaidng mechanisms ourselves, as that functionality is concentrated inside AJS_ODL. Moreover, using method-call interception allows us to trigger resource retrieval without cluttering the methods that trigger the loading operation. In Example 1 above, someMethod can remain entirely free of the script-manipulation logic, or indeed any such clutter because the loading-code is applied externally to the method body. Casual observers of such code can concentrate solely on what the method does and how it does it, without even considering whether critical resource-dependencies are satisfied or not (or even the nature of the mechanism that satisfies those dependencies).

Furthermore, once a given loading prefix has executed, it can be detatched from the method to which it forms an affix. This means that subsequent calls to the method do not even trigger a redundant call to the loading manager – from the point of view of the application and the method, life goes on as if nothing had changed (except that a critical resource is now available, slightly less memory is in use, and performace is slightly better). We also lose the binding effect of the traditional approaches. Given that a loading prefix is applied externally to a given function, we can change the identity of the resource that the prefix loads on a per-application basis. Revisiting Example 1, someMethod would be stuck no longer with always loading SomeCode.js.

Still further benefits are available given that the AJS object can attach multiple prefixes to a given method. This means that a given part of an application can apply its own, very specific loading-requirements to the same method in happy ignorance of the fact that other parts of the application do precisely the same thing. Each loading-prefix for a given method will execute transparently from the point of view of the other prefixes, thus enabling the execution of a given method to cause the retrieval of a large number of different resources.

To learn how to use AJS_ODL, go now to the user guide.