Aug/093
Flex Configurations made easy
If you have worked on a project of any size using Flex and Cairngorm one of the issues that you might have encountered is how to allow the application to connect to the necessary services in different environments. For example with your local development you might connect to a Tomcat server running on your machine, but in the test environment it would connect to a different server. To solve this I used to have code that looked like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package com.softwarejesus.configuration.model { import com.adobe.cairngorm.model.ModelLocator; public class OldModelLocator implements ModelLocator { private static var modelLocator : OldModelLocator; public var SERVICE_URL:String = "http://localhost:8080/sampleServices/messagebroker/amf"; // public var SERVICE_URL:String = "http://devServer:8080/sampleServices/messagebroker/amf"; public static function getInstance() : OldModelLocator { if ( modelLocator == null ) { modelLocator = new OldModelLocator; } return modelLocator; } public function OldModelLocator() { if ( modelLocator != null ) throw new Error( "Only one ModelLocator instance should be instantiated" ); } } } |
You’ll notice that I have different endpoints commented out in my ModelLocator. In order to create a Test build, I would un-comment the second line and rebuild the application, but I was never entirely happy with that solution.
What some coworkers and I came up with, was creating a simple configuration XML that would reside in the same directory as the generated SWF. At startup, the application would load any environment specific constants before any real work began.
Now, how did this impact my code? The only major deviation from the standard Cairngorm pattern is that we had to wait to add the Services to our application until after the application had received confirmation that the configuration has been loaded.
As you’ll see here my Services.mxml code looks exceptionally ordinary.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?xml version="1.0" encoding="utf-8"?> <ServiceLocator xmlns="com.adobe.cairngorm.business.*" xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ import com.softwarejesus.configuration.model.ConfigModelLocator; [Bindable] private var serviceUrl : String = ConfigModelLocator.getInstance().SERVICE_URL; ]]> </mx:Script> <mx:RemoteObject id="sampleService" destination="sampleService" showBusyCursor="true" endpoint="{serviceUrl}"/> </ServiceLocator> |
Here is how I’ve written my ModelLocator, notice that when I instantiate the model, I retrieve the configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | package com.softwarejesus.configuration.model { import com.adobe.cairngorm.control.CairngormEvent; import com.adobe.cairngorm.control.CairngormEventDispatcher; import com.adobe.cairngorm.model.ModelLocator; import flash.events.Event; import flash.events.IOErrorEvent; import flash.net.URLLoader; import flash.net.URLRequest; import mx.controls.Alert; public class ConfigModelLocator implements ModelLocator { private static var modelLocator : ConfigModelLocator; private static const CONFIG_FILE_NAME:String = "config.xml"; public var SERVICE_URL:String; public static const CONFIG_LOAD_COMPLETE:String = "ConfigLoaded"; public static function getInstance() : ConfigModelLocator { if ( modelLocator == null ) { modelLocator = new ConfigModelLocator; modelLocator.loadXML(); } return modelLocator; } public function ConfigModelLocator() { if ( modelLocator != null ) throw new Error( "Only one ModelLocator instance should be instantiated" ); } public function loadXML():void { var configPath:String = CONFIG_FILE_NAME; var loader:URLLoader = new URLLoader(new URLRequest(configPath)); loader.addEventListener(IOErrorEvent.IO_ERROR, handleIOError); loader.addEventListener(Event.COMPLETE, handleXML); } private function handleXML(event:Event):void { var xml:XML = new XML((event.currentTarget as URLLoader).data); SERVICE_URL = xml.system.serviceUrl; CairngormEventDispatcher.getInstance().dispatchEvent(new CairngormEvent(CONFIG_LOAD_COMPLETE)); } private function handleIOError(event:Event):void { Alert.show("Unable to open config.xml. Please contact the your IT representative"); } } } |
Tying it all together, in my Application file, we I initialize the app, I retrieve the model, and add a listener for when it has completed the configuration process. Once the configuration complete event has been dispatched, I know I can now add the services and get to work making Service Calls
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="doInit()"> <mx:Script> <![CDATA[ import com.adobe.cairngorm.control.CairngormEventDispatcher; import com.softwarejesus.configuration.services.Services; import com.adobe.cairngorm.control.CairngormEvent; import com.softwarejesus.configuration.model.ConfigModelLocator; private function doInit():void { ConfigModelLocator.getInstance(); CairngormEventDispatcher.getInstance().addEventListener(ConfigModelLocator.CONFIG_LOAD_COMPLETE, addServices); } private function addServices(event:CairngormEvent):void { var serv:Services = new Services(); addChild(serv); } ]]> </mx:Script> </mx:Application> |
I found this solution to be perfectly suited to address an issue I ran into on another project. In that project, the client was a manufacturer of goods, and had distributors that sold his products. Initially it was set up so that when someone logged in they could browse through the entire catalog of products. A couple of months in, he told me that each of his distributors had a finite set of products they could sell, and that none of them could sell all of the products. As a result, the distributors could not show their customer’s the app, for fear that the customer would decide to purchase something they were not authorized to sell.
The solution I came up with was to host the web services, and all the assets (and believe me, the number of assets was disgusting) centrally, and develop a swf that could be given to the distributors to host on their respective websites. Along with the swf, the customer was also given a config.xml that would determine which products were available through the application. This solution allowed me to introduce brand new functionality and all I had to do was implement one new service call.
Sorry, the comment form is closed at this time.
No trackbacks yet.
12:57 pm on August 19th, 2009
Really great post. We would love to see contributions like this is the Flex cookbook; http://www.adobe.com/go/flex_cookbook.
Ed Sullivan
12:00 pm on August 20th, 2009
Or you can do this:
var channel:AMFChannel = new AMFChannel(”my-amf”, “http://{server.name}:{server.port}/messagebroker/amf”);
Flex will automatically fill in the stuff based on the server that is hosting the SWF. No need to load a configuration xml file at startup.
12:17 pm on August 20th, 2009
Paul, thanks for the feedback. I’ve used that as well, and while it does work in the right situations there are a few issues that might be encountered:
1.) Launching it from eclipse (using the default settings) can be problematic.
2.) Connecting to any environment-specific service not hosted on your server still requires additional logic.