Accessing Remote Services

Before starting this tutorial, we recommend you to read the introduction to the RPC practices and patterns and their implementation in the Foundry: “Remote Procedure Call“. This howto follows the step-by-step sample “ActionScript Foundry: a beginner’s guide“.

Abstract

As the article sats “Remote Procedure Call“, Flex performs asynchronous calls to remote services. Most of the time, the way the call is triggered and handled will correspond to the sequence depicted in the diagram below.

Remote Services - Lifecycle

Remote Services - Lifecycle

Accessing a Java Facade

First we create a method in ViewHelper to call getCustomers method in the controller.

// com/company/sample/view/CustomersViewHelper.as
 
...
    public class CustomersViewHelper extends ViewHelper
    {
 
...
        public function getCustomers() : void
        {
        /**
         * We call getCustomers by using the controller singleton instance
         */
                SampleController.getInstance().getCustomers();
        }
 
    }
...

Updating the View and the ViewHelper

In the next step we will update the CustomerView and its ViewHelper. We start by adding a DataGrid that will contain customers and a button to update the customers collection.

<?xml version="1.0" encoding="utf-8"?>
<!-- com/company/sample/view/CustomersView.mxml -->
<view:HBoxView
...
    <!-- the DataGrid provider is customers member of the ViewHelper -->
    <mx:DataGrid
    	id="customerDG"
    	width="85%"
    	height="100%"
    	dataProvider="{helper.customers}">
    	<mx:columns>
    		<mx:DataGridColumn dataField="firstName" headerText="First Name" />
    		<mx:DataGridColumn dataField="lastName" headerText="Last Name" />
    	</mx:columns>
    </mx:DataGrid>
 
    <mx:Button
    	label="Update"
    	click="helper.getCustomers()"/>
...
</view:HBoxView>

The next step is to add the Bindable metadata tag to the customers getter in CustomersViewHelper, so the getter can be used as a bindable property.

// com/company/sample/view/CustomersViewHelper.as
 
...
    public class CustomersViewHelper extends ViewHelper
    {
 
...
        /**
         * Accessor to the customers collection.
         */
        // Every time when customersChange event is triggered
        // th customers collection will be updated
        [Bindable("customersChange")]
        public function get customers() : ArrayCollection
        {
            return SampleModel(
                       ModelLocator.getInstance().getModel("SampleModel")
                   ).getCustomers();
        }
 
    }
...

Creating the BusinessDelegate

See more about the BusinessDelegate class there.

Before adding the function to the controller we must create our business delegate. First, we need first to create a SampleDelegate class inside the com.company.sample.business package. Then we must initialise the business delegate by overriding the initializeDelegates method. Then inside our business delegate we create the getCustomers method. Inside our method we link the asynchronous completion token to an instance of IBusinessResponder. The responder will be used to handle service responses.

// com/company/sample/business/SampleDelegate.as
 
package com.company.sample.business
{
    import org.servebox.foundry.service.AbstractBusinessDelegate;
    import org.servebox.foundry.service.IBusinessResponder;
 
    public class SampleDelegate extends AbstractBusinessDelegate
    {
        public function SampleDelegate( service : Object = null )
        {
            super( service );
        }
 
        public function getCustomers ( responder : IBusinessResponder) : void
        {
          // The remote Java facade provides a getCustomers method.
          // We do not have to declare a remote object instance,
          // as the controller will give us the appropriate one.
          // The linkResponderToCallToken is an helper method
          // that links the BusinessResponder to the service
          // completion token (ACT).
          linkResponderToCallToken( getService().getCustomers(), responder );
        }
    }
}

The remote call is performed by the controller, using the CustomerResponder class (see below) as the responder.

// com/company/sample/control/SampleController.as
 
package com.company.sample.control
{
    import org.servebox.foundry.control.AbstractController;
 
    import com.company.sample.business.SampleDelegate;
    import com.company.sample.business.responder.CustomerResponder;
    import com.company.sample.model.SampleModel;
 
    import flash.utils.getQualifiedClassName;
 
    /**
     * Sample implementation of the Controller participant.
     */
    public class SampleController extends AbstractController
    {
        /**
         * We create a shortcut to the BusinessDelegate instance.
         */
        protected function getSampleDelegate () : SampleDelegate
        {
            SampleDelegate( getBusinessDelegate( getQualifiedClassName( SampleDelegate ) ) );
        }
        /**
         * This method is automatically triggered
         * at application startup.
         */
        override public function initializeDelegates():void
        {
       	    var remoteService: RemoteObject = new RemoteObject();
            // "sampleFacade" is the desintation id of the Java facade
            // declared in remoting-config.xml on the server-side.
	    remoteService.destination = "sampleFacade";
            // Registering the BusinessDelegate for later retrieval
	    registerBusinessDelegate( new SampleDelegate( remoteService), getQualifiedClassName( SampleDelegate ) );
        }
 
        // We create an instance of CustomersResponder that will handle service response
        public function getCustomers () : void
        {
            getSampleDelegate().getCustomers( new CustomersResponder() );
        }
    }
}

Creating the Responder

The main purpose of a responder is to manage the responses of a specific method of the remote service. Every responder should implement the IBusinessResponder interface. The fault and result methods are intended for handling the system errors (network failure for example) or to propagate the results to other participants in the sequence.

// com/company/sample/business/responder/CustomersResponder.as
 
package com.company.sample.business.responder
{
    import com.company.sample.model.SampleModel;
 
    import mx.collections.ArrayCollection;
    import mx.controls.Alert;
 
    import org.servebox.foundry.model.ModelLocator;
    import org.servebox.foundry.service.IBusinessResponder;
 
    public class CustomersResponder implements IBusinessResponder
    {
        // When the service call is successful we store
        // the customer list sent by the service in the model
        public function result(data:Object):void
        {
            // We will handle service response in this function
        }
 
        // If the service call fails we trigger fault method
        // and show an alert box
        public function fault(info:Object):void
        {
            Alert.show( "Error getting customer list" );
        }
 
    }
}

Handling Data Service responses

We assume here that the method getAllCustomers returns a TransferObject named AllCustomersTO.

// com/company/sample/value/transfer/CustomerTO.as
 
package com.company.sample.value.transfer
{
    import mx.collections.ArrayCollection;
 
    import org.servebox.foundry.transfer.TransferObject;
 
    public class CustomerTO extends TransferObject
    {
        // This property will contain the customers collection
        // customers is a collection of CustomerVO
        public var customers : ArrayCollection;
    }
}

The CustomerVO class will contain two properties that will be shown in the DataGrid columns.

// com/company/sample/value/vo/CustomerVO.as
 
package com.company.sample.value.vo
{
    import org.servebox.foundry.value.AbstractValueObject;
 
    public class CustomerVO extends AbstractValueObject
    {
    	[Bindable]
        public var firstName : String;
 
        [Bindable]
        public var lastName : String;
    }
}

The final step is to actually handle the result. We modify the result handler in the CustomerResponder

// com/company/sample/business/responder/CustomersResponder.as
 
...    
 
    public class CustomersResponder implements IBusinessResponder
    {
        // When the service call is successful we store
        // the customer list sent by the service in the model
        public function result(data:Object):void
        {
            var allCustomersTO : CustomerTO = CustomerTO( ResultEvent(data).result );
            SampleModel( ModelLocator.getInstance().getModel("SampleModel") ).setCustomers( allCustomersTO.customers );
        }
    }
...

When storing the customer list the Model, a notification is broadcast to observers instances so the views can be updated. Details about the observer pattern can be found in the previous howto : “ActionScript Foundry : a beginner’s guide“.

Accessing a SOAP service or a REST Service

Calling a SOAP service

When calling a SOAP service, the syntax is a little more different in the initializeDelegates method in the SampleController.

// com/company/sample/control/SampleController.as
 
...
    public class SampleController extends AbstractController
    {
        ...
        override public function initializeDelegates():void
        {
            var service : WebService = new WebService();
            service.wsdl = "http://api.google.com/GoogleSearch.wsdl";
            service.soap = "http://api.google.com/GoogleSearch";
            service.useProxy = false;
            service.loadWSDL();
            // Registering the BusinessDelegate for later retrieval
	    registerBusinessDelegate( new SampleDelegate( service ), getQualifiedClassName( SampleDelegate ) );
        }
    }
...

Calling a REST service

A REST service response is encoded in XML. In the initializeDelegates method of the SampleController we must create a HTTPService instead of WebService.

// com/company/sample/control/SampleController.as
 
...
    public class SampleController extends AbstractController
    {
        ...
        override public function initializeDelegates():void
        {
            var restService : HTTPService = new HTTPService();
            restService.useProxy = false;
            restService.method = "post";
            restService.resultFormat = "e4x";
            service.url = "http://www.myhttpservice.com/getCustomers";
            // Registering the BusinessDelegate for later retrieval
	    registerBusinessDelegate( new SampleDelegate( restService ), getQualifiedClassName( SampleDelegate ) );
        }
    }
...

In the case of SOAP / HTTP services, we strongly encourage you to parse / convert the results to strong-typed objects, to increase scalability and maintanability of the GUI part of the application. The creation of value objects from the XML returned by such services requires a really small effort.

// com/company/sample/parser/SampleParser.as
 
pakcage com.company.sample.parser
{
    import com.company.sample.value.transfer.CustomerTO;
    import com.company.sample.value.vo.CustomerVO;
    import com.company.sample.services.parser.IParser;
 
    public class SampleParser implements IParser
    {
        public function parse ( xml : XML ) : CustomerTO
        {
            var customerTO : CustomerTO = new CustomerTO();
 
            for each ( var customer : XML in xml.customer )
            {
                var customerVO : CustomerVO = new CustomerVO();
                customerVO.firstName = customer.firstName;
                customerVO.lastName = customer.lastName;
                customerTO.customers.addItem( customerVO );
            }
            return customerTO;
        }
    }
}

After creating the parser class, we will see below how to use it to parse the HTTP service response.

// com/company/sample/business/responder/CustomersResponder.as
 
...    
 
    public class CustomersResponder implements IBusinessResponder
    {
        // When the service call is successful we store
        // the customer list sent by the service in the model,
        // after parsing it the XML data in order to create
        // value objects.
        public function result(data:Object):void
        {
            var customerParser: SampleParser = new SampleParser();
            var allCustomersTO : CustomerTO = customerParser.parse( ResultEvent(data).result as XML );
            SampleModel( ModelLocator.getInstance().getModel("SampleModel") ).setCustomers( allCustomersTO.customers );
        }
    }
...

Conclusion

As you have seen previously, handling the remote services data flow is not a very complex process, but the design of the application should be done carefully to avoid later problems. Using the ActionScript Foundry lets the programmer use a simple mechanism to automate a part of the processing and to apply a well-suited structure.

Previous Step

Tuesday, November 18th, 2008 No Comments by Jeff Mathiot