EGL Development User Group

EGL Development User Group

EGL Development User Group

The EGL Development User Group is dedicated to sharing news, knowledge, and insights regarding the EGL language and Business Developer product. Consisting of IBMers, HCL, and users, this community collaborates to advance the EGL ecosystem.

 View Only

Super-size your webapp: Building highly scalable web apps using dynamic loading and browser-side caching

By Jiyong Huang posted Wed April 01, 2020 09:18 PM

  
Whether you write a webapp by hand, or migrate it from an existing green screen application, scalability will be a concern sooner, rather than later. Your application will grow to incorporate different features or concerns. It will get bigger and bigger over time. At some point, loading the application will become noticeably slow, especially over slower networks.

Instead of loading one monolithic application into the browser, it would be more efficient to load modules piecemeal and thereby increase the responsiveness of the browser application. This brings us to the topic of incremental loading of program modules.

Incremental loading is nothing new. Java has had it from day one in the form of class loaders that load classes only when they are needed. More recently, full-fledged component models were added to Java, such as the Eclipse plugin model and the OSGi bundle framework. In each, the intent is to split up an application into smaller sub-components, reuse the components, and compose a running application dynamically at runtime.

Dynamic loading is also used other domains, such as desktop applications, running on platforms such as Windows. Executable applications load modules in the form of DLLs. The executable file gets smaller, and DLLs are only loaded once into main memory and their code segments are reused by multiple applications. The main result is a reduction in memory by sharing executable code across processes.

The main motivation for dynamic loading of web apps is a bit different and intends to reduce download times, to decrease startup times, and to reduce memory usage. Let's do some math.... Assuming we have an application with 200 pages, and each page needs about 2K of JavaScript code to describe it. Furthermore, we have a main module that is 75K. This leads to the following download sizes:


This table shows that for dynamically loaded web apps, the initial cost to bring up the application is dramatically smaller. Only 100K + 2K needs to be downloaded to show the first page. For a statically generated web app, we need to load all hundreds or thousands of pages plus the main page, before we get to show any individual page. Each subsequent page is then free in the monolithic case as we already downloaded them all, but by then our customer probable gave up already.

Dynamically loading web apps

So, now that we understand the benefits, how to go about designing dynamic loading for web apps? In essence, web applications consist of executable code in the form of JavaScript libraries. Additional artifacts such as images, CSS files, and static HTML are loaded directly by the browser and are already cached pretty well by the browser, so we can ignore those artifacts. The JavaScript is what we are interested in.

To support dynamic loading of JavaScript modules we need to do 3 things:
1. bundle up fragments of JavaScript code into a module so that we can dynamically load it in an Ajax call
2. decouple modules so that dependencies between modules become symbolic, rather than hard-coded
3. figure out how 2 modules can communicate with each other when they actually never met before

Packaging JavaScript into bundles

EGL Rich UI has the perfect built-in abstraction for modules and it is called the RUIhandler. A RUIHandler is a standalone program that can be run in the browser all by itself. In that case it is parented to the document body. However, a RUIHandler can also be attached to another parent RUIHandler. This is a very natural decomposition of complexity, and almost all Rich UI applications I have seen use this mechanism automatically.

When a RUIHandler is compiled, the EGL build tools use the static type information that is available in EGL to determine the transitive closure of all the components required by this given RUIHandler. This is an automatic process, and the EGL developer need not worry about this process at all. The tooling creates the deploy XML file when compiling the RUIHandler.

When the RUIHandler is deployed, the transitive closure, captured in the deploy XML file, is used to generate an HTML file to host the application. That closure is a perfect candidate for use as a module definition.

This is what a typical deploy.xml file might look like:
<ruiDeploy package="ui" partName="Page2" ...>  
<includes>
<include>dojo/widgets/DojoBase.js</include>
<include>config/includeDojo.html</include>
 <include>dojo/widgets/DojoButton.js</include>
<include>ui/Page2.js</include>
<include>com/ibm/egl/rui/widgets/HTML.js</include>
</includes>
...
</ruiDeploy>

The above module description lists all the components needed recursively by ui.Page2. It is used by the dynamic loader described in this blog entry to figure out what JavaScript to download and add to the current browser document to instantiate the module dynamically.

Decoupling modules

Normally, when using nested RUIHandlers in EGL Rich UI, the parent handler declares an instance of a given type. That handler instance is then used in the composition. It will also be added to the transitive closure of the parent handler. To avoid that from happening we want to decouple these two modules. Therefore, the parent handler cannot directly refer to the type of the child handler. Eclipse solves this by using Java interfaces, and a string in a plugin.xml or bundle manifest file. Windows has special support in the linker to include stubs in the generated executable. In the end, strings and not types are the solution. To load a module in EGL Rich UI, we use the following syntax:

handler MainHandler type RUIhandler {&hellip;, onConstructionFunction = start }  
...
function start()
// Load the first page
DynamicLoader.loadHandler("ui.Page1");
end

It is essential that MainHandler does not directly refer to the type Page1, but instead uses a String referring to the type. Again, this is to avoid the deep closure computation the linker performs when our application is published.

Communication across dynamically loaded modules

Earlier we said modules don’t know about each other. They share no type references (to avoid loading too much in any of the child components). Neither does EGL have type abstractions like Java interfaces to separate API from concrete implementations. However, a simple and flexible solution exists: the InfoBus. EGL Rich UI supports application-level events to be subscribed to and to be broadcasted to various listeners. Internally, the InfoBus uses the OpenAjaxHub, a flexible and highly scalable publish/subscribe architecture based on the open Ajax project.

Here is how a parent module can detect loading of a child module:

  function start()        
//
// Listen to the event that the page has been loaded
//
InfoBus.subscribe(DynamicLoader.INFOBUS_EVENT_DONE, attach);
//
// Load the first page
//
DynamicLoader.loadHandler("ui.Page1");
end

//
// Respond to InfoBus event for successful loading of the module.
//
function attach(msg String in, object any in)
// receive the ModuleLoadEvent
module ModuleLoadedEvent = object;
// attach the resulting UI to the document
div.children = module.initialUI;
end


Whenever a RUIHandler is completely loaded and instantiated, the dynamic loader sends an event on the InfoBus, and the "attach" function will be called. In the sample above, we simply add the UI elements in the handler to the current document, thereby activating the newly loaded RUIHandler and enabling it for user interaction.

The InfoBus can also be used between components, of course. One module might publish an InfoBus event to indicate the current selection has changed. Numerous other dynamically loaded modules may subscribe to this very event and update their own UI, generate some trace events, start a timer, or do whatever suits them.

Also notice that the dynamic loader is asynchronous, as we use Ajax calls to load the modules over the network.

Debugging the dynamic loader

Now that we abstracted out the actual loading of modules in an asynchronous manner, we have the perfect hook for easily implementing concerns such as feature coverage, tracing, and debugging information. The EGL Rich UI dynamic loader already comes with a very useful visual tracer that uses the InfoBus events generated by the dynamic loader to provide insight into how the various components are loaded and added to the document. It generates the following information when Page1 is loaded:
com.ibm.dynamic.loader.loadedDeployFile ui.Page1
com.ibm.dynamic.loader.loaded ui/Page1.js
com.ibm.dynamic.loader.doneLoading ui.Page1

When we trigger the loading of Page2, its deploy file is used as shown above:

com.ibm.dynamic.loader.loadedDeployFile ui.Page2
com.ibm.dynamic.loader.skipped config/includeDojo.html (not JavaScript)
com.ibm.dynamic.loader.loaded dojo/widgets/DojoButton.js
com.ibm.dynamic.loader.loaded ui/Page2.js
com.ibm.dynamic.loader.doneLoading ui.Page2

Each JavaScript component is eval-ed. A file like includeDojo.html, which is needed for the EGL Rich UI Dojo support, is skipped. Reloading the same component twice is wasteful and may lead to unexpected side effects. Therefore, before a component is eval-ed we make sure it is not yet known.

Caching modules in the browser

The asynchronous aspect of module loading makes it easy to insert different concerns. One of them is caching. Browsers offer client-side storage using technologies such as HTML5 storage or Google Gears.

Rather than loading modules from the network, the dynamic loader can try the SQL database installed in the browser first. If the it receives a cache hit, it loads the module from the browser storage. If it receives a cache fail, it would consult the network, download the module, store it in the cache, and then return it to the application.

Once the application has warmed up the cache, a dynamic loader that uses browser-side storage would not need to load any modules over the network anymore. This can have a high impact on responsiveness of web apps as indicated in the chart below:


Browser-side caching is more experimental, and requires the use of not yet widely available HTML5 or unsupported Google Gears. Therefore, it is not included in the attached version of the dynamic loader.



0 comments
0 views

Permalink