Design Patterns in RIA

This article talks about a number of design patterns that can be implemented in the client-tier to enable the building of robust, scalable, and maintainable RIAs using techniques and patterns that will be familiar to J2EE developers. This document assumes that users are familiar with Adobe Flex.

RIA Model-View-Controller (MVC) Pattern

Adobe Flex is based on a Model-View-Controller (MVC) architecture. Flex primarily focuses on the view part of the MVC architecture. It also provides some controller logic to help communicate with remote systems. Although the Flex application can be considered part of the View in a distributed MVC (Model 2 MVC), it also implements its own MVC architecture at the client side. A Flex application has its own view components (typically written in MXML), model components (representing data at the client side), and controller components (responsible for the communication with back-end systems).



RIA Front Controller Pattern

The Front Controller implementation can be achieved using ActionScript 2.0 with a collaboration of two key classes that work in an event-driven manner.
  • Controller - Configured to listen for specific events and delegate control to specific worker classes when these events occur
  • EventBroadcaster - Responsible for broadcasting events to the controller in response to user-gestures, changes in application state and so on.

In Flex, components can have events attributed to them. Consider the example of a simple login screen. An event can be specified to the Login button like below:

<mx:Button label="Login" click="EventBroadcaster.getInstance().broadcastEvent( ICommand.LOGIN_COMMAND )" />



The click event is generated when the user clicks the button. On the click event EventBroadcaster is invoked to broadcast the event as:

EventBroadcaster.getInstance().broadcastEvent(Command.LOGIN_COMMAND )



The argument to the broadcastEvent method is a String which is the event name.

The role of Front Controller is to first register all the different events that it is capable of handling against worker classes, called Command classes. On hearing an application event, the Front Controller will look up its table of registered events, find the appropriate command for handling of the event, before dispatching control to the Command by calling its execute() method.

In the Login application that is being discussed,

import com.inkriti.framework.flex.control.*;
import com.inkriti.flex.demo.commands.*;

class com.inkriti.flex.demo.control.DemoController extends FrontController
{
   public function DemoController()
   {
      addCommand( ICommand.LOGIN_COMMAND, new LoginCommand() )
   }
}



This is the code for the DemoController that is included in the Login application MXML file in the previous section. All the events in the application are registered by adding Commands. For more information on Command pattern look at the next section.

RIA Command Pattern

By using the Command Pattern to manage the "work" associated with each request, a scalable architecture can be achieved in a simple fashion. As the complexity of the application increases, the number of use-cases also typically increases - each use-case largely corresponds to the creation of new command class and the registering of the event name with the Controller, which should cause control to pass to this command class. Lets look into the LoginCommand class for more details.

import com.inkriti.framework.flex.business.Responder;
import com.inkriti.framework.flex.commands.Command;
import com.inkriti.framework.flex.control.Event;
import com.inkriti.framework.flex.view.ViewLocator;
import com.inkriti.flex.demo.business.LoginDelegate;
import com.inkriti.flex.demo.vo.LoginVO;
import com.inkriti.flex.demo.vo.UserListVO;
import com.inkriti.flex.demo.view.IView;

class com.inkriti.flex.demo.commands.LoginCommand implements Command, Responder
{
   public function LoginCommand()
   {
      loginDelegate = new LoginDelegate( this );
   }

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

   public function execute( event:Event ) : Void
   {
      var loginViewHelper = ViewLocator.getInstance().getViewHelper( IView.LOGIN_VIEW );
      var username = loginViewHelper.getUsername();
      var password = loginViewHelper.getPassword();

      var loginVO = new LoginVO();
      loginVO.username = username;
      loginVO.password = password;

      loginDelegate.login( loginVO );
   }

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

   public function onResult( event : Object ) : Void
   {

      var demoViewHelper = ViewLocator.getInstance().getViewHelper( IView.DEMO_VIEW );

      var userListVO : UserListVO = event.result;
      trace("Got User List");
      trace("Number of user=" + userListVO.userVOArray.length);
      demoViewHelper.switchToMainView( userListVO );
   }

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

   public function onFault( event : Object ) : Void
   {
      var loginViewHelper = ViewLocator.getInstance().getViewHelper( IView.LOGIN_VIEW );
      loginViewHelper.loginFailed();
   }

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

   private var loginDelegate: LoginDelegate;
}



The LoginCommand class implements Command class and the main method to be seen is the execute() method. In the execute() method the values of the login user name and password is extracted from LoginViewHelper. ViewHelper classes encapsulate all the elements in the MXML file. In the application discussed, a view is declared in Login.mxml as:

<demoView:LoginViewHelper name="{IView.LOGIN_VIEW}" view="{ this }" />



So the LoginViewHelper class has access to the elements in Login.mxml file.

import com.inkriti.framework.flex.view.ViewHelper;

class com.inkriti.flex.demo.view.LoginViewHelper extends ViewHelper
{
   public function LoginViewHelper()
   {
   }

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

   public function getUsername() : String
   {
      return view.username.text;
   }

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

   public function getPassword() : String
   {
      return view.password.text;
   }

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

   public function loginFailed() : Void
   {
      view.statusMessage = "Your username or password was wrong, please try again.";
   }
}



Going back to the LoginCommand classes's execute() method, first get the instance of LoginViewHelper and get the values of the form elements in Login view and populate a Value Object LoginVO (for more on Value Objects see next section). Finally it calls the delegate to do the necessary business logic.

LoginCommand also implements a Responder which has to methods: onResult() and onFault().
  • The onResult method interface is used to mark the method on a concrete Responder that will handle the results from a successful call to a server-side service. The actual data returned will be held in event.result where event is the argument in onResult method.
  • The onFault method interface is used to mark the method on a concrete Responder that will handle the information from a failed call to a server-side service. The actual data returned will be held in event.fault
Typically in the onResult method first get the instance of the ViewHelper class which represents the view on success. Secondly, get the resulting Value Objects from event.result. Finally pass on the resulting Value Object to the ViewHelper. In the application discussed, the ViewHelper is got in the onResult method as

var demoViewHelper = ViewLocator.getInstance().getViewHelper( IView.DEMO_VIEW );



The resulting ViewHelper in our example is the DemoViewHelper which shows list of user in the system. This view was registered in the main applications MXML file.

Then the resulting VO is referenced as:

var userListVO : UserListVO = event.result;



Finally the VO is passed to the DemoViewHelper view as:

demoViewHelper.switchToMainView( userListVO );



Similarly in onFault method the same procedure is followed. In the application discussed, the same login screen is shown with a failed message.

So first get the view as

var loginViewHelper = ViewLocator.getInstance().getViewHelper( IView.LOGIN_VIEW );



Finally set the custom message shown on the screen as:

loginViewHelper.loginFailed();



The LoginViewHelper has a method loginFailed() as:

   public function loginFailed() : Void
   {
      view.statusMessage = "Your username or password was wrong, please try again.";
   }



Since LoginViewHelper encapsulates all the elements defined in Login.mxml it has access to statusMessage variable defined in Login.mxml (see the previous section).

RIA Business Delegate Pattern

The Business Delegate represents a clear point of integration between the client and the server. Business Delegate typically fulfills its role in collaboration with the Service Locator; It uses Service Locator to locate and look up remote services, such as web services, remote Java Objects or HTTP services. Once located, Business Delegate invokes these services on behalf of the class that has delegated responsibility to it for business logic invocation.
As seen in the previous section, Command class is responsible for deciding which business services should be invoked and ensures that the appropriate data is collected to call these services.

The Service Locator class looks up for the service requested by the name of the service. ServiceLocator class maintains an array of services. In the application discussed, ServiceLocator is subclassed and services defined using the following code:



<?xml version="1.0" encoding="utf-8"?>
<inkriti:ServiceLocator xmlns:mx="http://www.Adobe.com/2003/mxml" xmlns:inkriti="com.inkriti.framework.flex.business.*" >

  <mx:RemoteObject id="loginDelegate" source="com.inkriti.flex.demo.business.LoginDelegate"
                protocol="http"
      result="event.call.resultHandler( event )"
                fault="event.call.faultHandler( event )">
  </mx:RemoteObject>

</inkriti:ServiceLocator>



Lets looks into the delegate class. Delegate class has methods that implement the business logic for a particular use-case. In the application discussed, the LoginDelegate has login method which calls the login method defined in the underlying Java layer.

import com.inkriti.framework.flex.business.*;
import com.inkriti.flex.demo.business.*;
import com.inkriti.flex.demo.vo.*;
import mx.utils.Delegate;

class com.inkriti.flex.demo.business.LoginDelegate
{
   public function LoginDelegate( responder : Responder )
   {
      this.service = ServiceLocator.getInstance().getService( IDelegate.LOGIN_DELEGATE );
      this.responder = responder;
   }

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

   public function login( loginVO : LoginVO ): Void
   {
      var call = service.login( loginVO );

      call.resultHandler = Delegate.create( Object( responder ), responder.onResult );
      call.faultHandler = Delegate.create( Object( responder ), responder.onFault );
   }

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

   private var responder:Responder;
   private var service:Object;
}



In the constructor, it gets the instance of the underlying service by means of Service Locator (discussed earlier). ServiceLocator class has access to definitions. Once it gets the appropriate service, it calls the methods on the service. In the application discussed:

var call = service.login( loginVO );



After executing the remote service methods, the resultHandler and the faultHandler are set as discussed earlier:

call.resultHandler = Delegate.create( Object( responder ), responder.onResult );
call.faultHandler = Delegate.create( Object( responder ), responder.onFault );



Delegate is a standard ActionScript 2.0 class shipped with Flex which allows developers to create a function wrapper and run the function.
In the application discussed, a wrapper is created for Responder Object which is passed through the constructor of the LoginDelegate and call the appropriate function.

RIA Value Objects

Data passed between various layers in the enterprise application is encapsulated in the form of Value Objects. Value Objects in Java layer are basic java beans which have set and get methods for each private member of the bean. Value Objects allow to do the following:
  • Encapsulate the implementation of business objects
  • Establish a "currency" for data passed between the various tiers on the client.
  • Establish a common object model on the client and the server.
  • Enable data to be passed transparently between the client and the server, and treated the same manner on both sides of the "wire"

Summary

This article explained in brief the design patterns for RIA development that are likely to be familiar to J2EE developers. This article would have given the reader a good understanding of how to develop RIAs with Adobe Flex and learned how ActionScript 2.0 and MXML can work together to enable the development of RIAs.

References

  • http://www.Adobe.com/flex
  • http://www.theserverside.com/articles/article.tss?l=Flex
  • http://www.Adobe.com/devnet/flex/articles/struts.html
  • http://www.richinternetapps.com/archives/000017.html
  • http://www.Adobe.com/devnet/mx/flash/articles/ria_dataservices.html
  • http://www.Adobe.com/devnet/mx/flash/articles/databinding.html