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:
getProducts(), on the
delegate 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.
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.
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.