Services orchestration for AJAX

Execute process definition on the client side

AJAX (Asynchronous JavaScript and XML) is now widely known as a technique used in client-side interactions. However, AJAX requires special coding for asynchronous requests and for server-side programming. This article proposes a new AJAX approach that executes the process definition on the client. It allows you to orchestrate services synchronously and apply the MVC (Model View Controller) pattern, so you will find coding similar to server-side programming. Before discussing the new approach, let's take a look at the problems with AJAX currently.

AJAX typically communicates with the Web server using asynchronous requests so that other tasks can be executed while waiting for a response. It enables the user to interact with the browser, rather than the standard Web application. But once you decide to use AJAX, you have to struggle with callback functions. Look at the following code that executes two functions:

Listing 1. Execute two functions

funcA();
funcB();

You may expect that funcB() executes after funcA() is complete. But on the client-side, we must check whether the code calls an asynchronous request. If it does, the functions execute in parallel. funcB() starts executing without waiting for a response from funcA(). In this case, the response of funcA() is processed in the callback function (e.g., cbA()), which means we must handle an additional function for each function. Also, the use of a callback function causes problems that break the process flow and complicates the orchestration of functions. If you want to execute another function, for example, funcC() after funcA() and funcB() are complete, you must implement logic to join the callback functions, as shown in Listing 2 and the parallel pattern illustrated in Figure 1. In the list and figure, callback functions for funcA() and funcB() are defined as cbA() and cbB(), respectively.

Listing 2. Join the two functions

var count = 2;
funcA();
funcB();

function cbA() {
   if (--count == 0) funcC();
}

function cbB() {
   if (--count == 0) funcC();
}
Figure 1. Parallel vs. sequential pattern

funcC() is now invoked sequentially; however, the function is tightly coupled with cbA() and cbB(). If funcC() needs to be replaced with funcD(), we must modify the callback functions; that is, the use of a callback function affects the reusability of funcA() and funcB().

Also, if you want to execute funcA() and funcB() synchronously, without blocking code, you must invoke funcB() in cbA(), as shown in Listing 3 and in the sequential pattern illustrated in Figure 1. cbA() and funcB() are also tightly coupled.

Listing 3. Execute two functions sequentially

funcA();

function cbA() {
   funcB();
}

In addition, as shown in these code listings, we must implement the flow control ourselves using JavaScript to make asynchronous and/or synchronous requests.

Services orchestration

Thus far, we have found the following problems with AJAX code:

  • Process flow interrupted by callback functions
  • Tight coupling among functions
  • Flow control is intertwined in functions

One reason why we are encountering problems is that AJAX is inherently based on an asynchronous model. A synchronous model, rather than an asynchronous one, is intuitive and more natural for most developers. It would prove helpful if, somehow, our asynchronous requests could be changed to synchronous ones. Moreover, the parallel pattern should be easily acceptable when the developer requires it, and flow control should be separated from the components properly. These requirements don't seem that special because they are standard for server-side development. Our goal is to apply server-side style to our client-side asynchronous model.

To achieve this goal, this article takes a process-centric approach that describes methods for execution as services in process definition XML using J-SOFA (Java/JavaScript Services Orchestration for Actions), an open source framework for orchestrating services. It provides two service orchestrators for the client and server. The client-side orchestrator invokes JavaScript functions, and invocations of Java objects and Web services are delegated to a server-side orchestrator, as shown in Figure 2.

Figure 2. Services to be executed by orchestrators

The services are invoked synchronously, so you simply describe tags step-by-step. The following XML represents sequential flow, which executes server-side methodA() and methodB(). The developer doesn't have to describe callback functions in the XML because J-SOFA makes asynchronous requests and handles the callback functions internally.

Listing 4. Sequential flow

<process>
   <service name="MyService" operation="methodA" runAt="server" />
   <service name="MyService" operation="methodB" runAt="server" />
</process>

If you need to apply a parallel pattern, it can be described using the fork tag, which splits the flow and asynchronously executes the tags in the body. The following code invokes methodA() and methodB() in parallel, and then exits from the fork tag after those two methods complete. You don't have to implement the code for forking and merging in your components.

Listing 5. Parallel flow

<process>
   <fork>
      <service name="MyService" operation="methodA" runAt="server" />
      <service name="MyService" operation="methodB" runAt="server" />
   </fork>
</process>

To execute the process, just call the Jsofa.execute() function with the XML path in your HTML:

Listing 6. JavaScript code that executes the process

Jsofa.execute("sample.xml");

In addition to the fork tag, J-SOFA currently supports the following basic tags:

  • service: Invoke an operation (JavaScript, Java object, or Web service)
  • httpRequest: Send an HTTP request (client-side only)
  • if: Execute if test condition is true
  • choose/when/otherwise: Execute one of the paths
  • forEach: Execute iteratively with loop counter or variable
  • while: Execute iteratively while test condition is true
  • group: Organize two or more tags into one group
  • sleep: Wait for specified time
  • subProcess: Execute a sub-process in another process space

Furthermore, a process can contain server-side tags using the runAt attribute. J-SOFA basically executes the tags on the client-side; however, when it finds the runAt attribute with the value server, the tag and the child nodes are transformed to the server and executed there. When the server-side process has completed, the updated context is sent back to the client, and the rest of the tags execute on the client.

Figure 3 shows a group tag containing the runAt attribute with server and child nodes (two service tags) executing on the server.

Figure 3. runAt attribute for execution on server

With J-SOFA, you don't have to care about callback functions and can easily split the flow for executions in parallel. This is the same approach used when programming on the server. It is no longer necessary to learn client-specific techniques. J-SOFA allows you to utilize the same coding style for client-side as well as server-side coding.

Item search sample

For a better understanding of the process definition in AJAX, let's look at one of the sample applications provided in J-SOFA. This sample application demonstrates an item search feature that searches a model by item code and displays the model and related information on the page. The screens and process steps are illustrated in Figure 4. On the initial page is a text box and a submit button. When the user types in an item code and submits the form, the model information, feature list, and logo list display one by one.

Figure 4. Screen flow of item search

To retrieve the data on the server-side, we call three operations—getModel(), getFeatures(), and getLogos()—implemented in the ModelService object. Among the three operations is a constraint that the getModel() operation must execute before getFeatures() and getLogos(). Although getModel() accepts an item code as its parameter, getFeatures() and getLogos() only accept the model ID. Thus, we need to execute getModel() first to get the model ID from the item code. In addition, the operations require ample time for retrieving the data in the real world due to database access. In this sample, the sleep() method executes in each operation to simulate a heavy load situation.

On the client-side, the JavaScript functions init(), setModel(), setFeatures(), setLogos(), and finalize() are available. They are executed to show or hide image and data on the HTML page.

These server-side operations and JavaScript functions are orchestrated as shown in Figure 5.

Figure 5. Process diagram of item search

The diagram represents the following steps:

  1. Execute the init() JavaScript function to display loading image.
  2. Execute the getModel() method on the server to get the Model object.
  3. When the response returns, execute the setModel() JavaScript function, which populates the model information on the HTML page.
  4. Split the flow into two streams, and execute getFeatures() and getLogos() on the server in parallel. (They can be executed in parallel because no constraints govern the execution order.)
  5. After getFeatures() completes, execute the setFeatures() JavaScript function to populate the feature list on the page.
  6. After getLogos() completes, execute the setLogos() function to populate the logo list on the page.
  7. Wait for the two streams to complete, then invoke the finalize() function to hide the loading image.

The above steps can be represented with the following process definition XML:

Listing 7. itemSearch.xml (process definition XML)

<process>
   <service operation="init" />

   <service name="ModelService" operation="getModel"
            returnVar="model" runAt="server">
      <parameter value="${itemCode}" type="int" />
   </service>

   <service operation="setModel">
      <parameter value="${context.model}" />
   </service>

   <set var="f1.model" value="${context.model}" />
   <set var="f2.model" value="${context.model}" />

   <fork>
      <group>
         <service name="ModelService" operation="getFeatures"
                  context="f1" returnVar="features" runAt="server">
            <parameter value="${model.id}" type="int" />
         </service>

         <service operation="setFeatures">
            <parameter value="${context.f1.features}" />
         </service>
      </group>

      <group>
         <service name="ModelService" operation="getLogos"
                  context="f2" returnVar="logos" runAt="server">
            <parameter value="${model.id}" type="int" />
         </service>

         <service operation="setLogos">
            <parameter value="${context.f2.logos}" />
         </service>
      </group>
   </fork>

   <service operation="finalize" />
</process>

And the XML is executed with the following HTML and JavaScript code:

Listing 8. JavaScript code to execute process in HTML

<html>
...
<script language="JavaScript" type="text/javascript">
<!--
function execProcess() {
   var context = new Array();
   context.itemCode = document.searchFrom.itemCode.value;
   Jsofa.execute("itemSearch.xml", context);
}
...
//-->
</script>
...
...
<form name="searchFrom">
   Item Code:
   <input type="text" name="itemCode" value="">
   <input type="button" value="Submit"
          onclick="javascript:execProcess(); return false;">
   <span id="loading"></span>
</form>
...
</html>

Once the user clicks on the Submit button, the execProcess() function fires. It sets the item code the user entered in the context array with key name itemCode and executes the itemSearch.xml by calling Jsofa.execute(). The context can be signified using the expression ${...} in the process, e.g., ${itemCode} for item code on the server.

In the process definition XML, the following steps are executed:

  1. Execute the init() JavaScript function.
  2. Execute getModel() on the server because the service tag contains runAt attribute with server. On the server, the getModel() method in the ModelService object is invoked with a parameter, itemCode, and the return value (Model object) is set in the context with name model.
  3. Execute the setModel() JavaScript function with the Model object.
  4. Set the Model object in sub-contexts f1 and f2, which will be passed to split flow.
  5. Split the flow and execute two group tags in parallel.
  6. Execute getFeatures() with the model ID on the server side, and the result is stored in the sub-context f1 with name features.
  7. After getFeatures() is complete, execute the setFeatures() function.
  8. As getFeatures() is executing, simultaneously execute getLogos() with the model ID on the server side; the result is stored in the sub-context f2 with name logos.
  9. After getLogos() completes, execute the setLogos() function.
  10. Wait for the two streams to complete, then execute the finalize() function.

This sample shows that components and XML are separated into the Model-View-Controller (MVC) pattern properly, as illustrated in Figure 6. The process flow is described in XML and is separated from the logic. So the developer can concentrate on implementing service components in Java for business logic and JavaScript for rendering. This is why J-SOFA makes AJAX easy.

Figure 6. MVC in item search
1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more