Accessibility

Table of Contents

Developing Flex RIAs with Cairngorm microarchitecture – Part 5: Server-side integration

Proxying Services with the Business Delegate

A particular service call, such as getProducts(), will often be called several times in several different places in the application. Furthermore, the results from these service calls may be used differently, depending upon the context in which the service call was made.

For instance, consider a service call that fetches the day's trading values for a particular stock. One command might render all these data points on a graph, another command might calculate the day's high and low values for the stock, and still another command might display the current value of the stock only. In this scenario, you could use three different services calls to service.getStockInformation( "ADBE" ) for the three different contexts.

To handle this consistently, however, Cairngorm delegates all the responsibility for business logic that locates a service and then invokes a method on it into a separate class—the Business Delegate class.

In Cairngorm Store, there are two Business Delegates, one for each of the services. Deciding how many delegates to have is a design decision left entirely up to the individual developer.

Here is the implementation of ProductDelegate.as:

package
com.adobe.cairngorm.samples.store.business
{
        import mx.rpc.IResponder;
        import com.adobe.cairngorm.business.ServiceLocator;
        import mx.rpc.events.FaultEvent;
        import mx.rpc.events.ResultEvent;
        import mx.rpc.AbstractOperation;
 
        public class ProductDelegate
        {
               public function ProductDelegate( responder : IResponder )
               {              
                       this.service = ServiceLocator.getInstance().getRemoteObject( "productService" );
                       this.responder = responder;
               }
               
               public function getProducts() : void
               {                      
                       var call : Object = service.getProducts();
                       call.addResponder( responder );
               }
        
               private var responder : IResponder;
               private var service : RemoteObject;
        }
 
}

A discussion of the Business Delegate makes the most sense in the context of a Command class that calls the delegate. In the case of ProductDelegate.as, this is the GetProductsCommand class:

package com.adobe.cairngorm.samples.store.command

{
        import mx.rpc.IResponder;
        import com.adobe.cairngorm.commands.ICommand;
        import com.adobe.cairngorm.control.CairngormEvent;
        import com.adobe.cairngorm.samples.store.business.ProductDelegate;
        import com.adobe.cairngorm.samples.store.model.ShopModelLocator;
        import com.adobe.cairngorm.samples.store.util.Comparator;
        import mx.rpc.events.ResultEvent;
        import mx.rpc.events.FaultEvent;
        import mx.controls.Alert;
        import mx.collections.ICollectionView;
        import mx.collections.Sort;
        import mx.collections.SortField;
        import mx.utils.ArrayUtil;
        
        public class GetProductsCommand implements ICommand, IResponder
        {
               public function execute( event : CairngormEvent ): void
               {
                       if( ShopModelLocator.getInstance().products == null )
                       {
                           var delegate : ProductDelegate = new ProductDelegate( this );
                           delegate.getProducts();
                       }
                       else
                       {
                               Alert.show( "Products already retrieved!" );
                               return;
                       }
               }
               
               //------------------------------------------------------------
        
               public function result( event : Object ) : void
               {                              
                       var products : ICollectionView = ICollectionView( event.result );
                       var model : ShopModelLocator = ShopModelLocator.getInstance();
                       
                       // sort the data
                       var sort :Sort = new Sort();
                       sort.fields = [ new SortField( "name", true ) ];
                       products.sort = sort;
                       products.refresh();
           
                       // set the products on the model
                       model.selectedItem = products[ 0 ];
                       model.products = products;
                       model.workflowState = ShopModelLocator.VIEWING_PRODUCTS_IN_THUMBNAILS;
               }
               
               //------------------------------------------------------------
        
               public function fault( event : Object ) : void
               {
                       var faultEvent : FaultEvent = FaultEvent( event );
                       Alert.show( "Products could not be retrieved!" );
               }
        }
 
}

Let's explore the interaction of these two classes in greater detail. The Command class creates a new instance of the Product Delegate pattern, passing itself ("this") to the constructor of the delegate. In this way, the Command class declares that it wants to handle the results of any calls made by the delegate. The object that wants to respond to any results is known to the delegate as the "responder."

By electing to act as a responder, the Command class implements the IResponder interface from Flex; this ensures that the developer adds a result() method to handle any delegate results and a fault() method to handle any delegate faults.

In the implementation of getProducts() on the Product Delegate, note the two lines of boilerplate code that you can add for each and every delegate method:

public function getProducts() : void
{                      
        var call : Object = service.getProducts();
        call.addResponder( responder );
}

Don't worry too much about the details of this incantation. These lines ensure that the results and faults from the server are routed back to the responder—in this case, to the Command class.

With this collaboration of classes in place, the flow of control is both simple and elegant:

  • Command class creates a business delegate
  • Command class calls business methods, such as getProducts(), on the delegate
  • Delegate worries about looking up the service and invoking the service
  • At some point in the future, the server returns results to the delegate
  • Delegate immediately passes these results to the result() method on the command (or invokes the fault() method on the command)

The result() method of GetProductsCommand extracts the array of products from the event representing the server results and stores them on the ShopModelLocator, while also setting selectedItem on the ShopModelLocator to the first product in the catalogue.

If any other commands have utility for a getProducts() command, they can also call the Business Delegate method. On large development teams, developers learn to check existing delegates to see if any other developers have implemented methods that they can reuse before invoking the services themselves.

Incidentally, the server-side implementation of getProducts() is a Java class that employs a data access object (DAO) to query a relational database (MySQL and HSQLDB are supported) using JDBC. Sound complicated? Well, it doesn't matter; that's the entire point of the Business Delegate: to hide the complexity of server-side implementation from client-side developers. The source is there if you are interested in the details.

Stubbing the Business Delegate

There is another great reason for using a Business Delegate pattern. Often, there is one set of developers responsible for the server-side development of an RIA and another team working on the Flex client. The Business Delegate class becomes a "contract" that both teams agree on; this contract acts as the single point of interface between client and server.

We have often consulted on projects where development has started with a Business Delegate that doesn't actually call any server-side services. Instead, the Business Delegate is a "stub" class that creates mock value objects in ActionScript and directly calls the result() method on the calling command.

Client-side developers can then call the Business Delegate, even though the data returned to them is dummy data. Once server-side development is in sync with client-side development, you can change the method calls on the Business Delegate to call all the way through to the server, with no further changes required in the Command classes, or in any of the MXML or ActionScript view code.

When working with Cairngorm, many developers wonder about the utility of the Business Delegate and whether their commands might not just as easily call services using the Service Locator. At Adobe Consulting, we prefer to have Command classes oblivious to the concept of server-side services by employing the Business Delegate as a proxy. The flexibility of Cairngorm, however, enables developers to build RIAs without Business Delegates if they prefer.

We find that the minimal amount of effort necessary to create these delegates and our tendency to refactor towards a Business Delegate anyway are sound reasons for including the Business Delegate pattern in all applications that we architect upon Cairngorm.

Where to go from here

You have now seen all of the design patterns that contribute to and collaborate in the Cairngorm microarchitecture.

You've learned how the Service Locator pattern can be used to encapsulate all the different service calls that your rich Internet application might make under a common class in a predictable location.

As legitimate as it may be to invoke these services from the various Command classes in your architecture (you are still free to do so if you so wish) the Cairngorm team has found that there are tangible benefits to moving all service invocations into a Business Delegate class, to which you delegate all business logic calls to the server. The Business Delegate pattern offers consistency, scalability, modularity, and reuse while acting as a clear contract and single point of integration between server- and client-side development teams.

This article revisited the Command pattern and described how to write commands that implement the IResponder interface, part of the Flex framework. These commands not only use the Business Delegate pattern to invoke services, but also advertise through the IResponder interface that they wish to assume the responsibility for handling any results that the Business Delegate receives in proxying service requests on their behalf.

It is important to understand the different Flex services available to you and how to write your own Service Locator classes. Likewise, we encourage you to become comfortable with writing your own Business Delegates—by accepting an IResponder class in the constructor, liaising with the Service Locator, and adding the necessary incantations to the pending call of each service request that ensure results are routed back to the responder.

To implement the final clause in any conversation between the user and the RIA, we explained how to add methods to commands that implement the Responder interface, allowing the commands to take any service call results and update the client-side state through the Model Locator design pattern.

In Part 6, the final article in this series, we will summarize and reinforce the main concepts behind the Cairngorm architecture. What may have initially appeared complex is actually easy to apply. You'll be able to follow the rapid addition of a completely new feature to the Cairngorm Store, confident because you are using the same best practices that thousands of Flex developers worldwide would also follow when faced with the same code base.

Developing Flex RIAs with Cairngorm Microarchitecture – Part 6: Rapid and Consistent Development with Cairngorm and Flex