Introduction
After implementing Part 1 of the Swiz + Passive View (PV) Example, I immediately decided that one thing I missed from the original Spring ActionScript (SAS) version was the ability to create services in an XML file that’s loaded at runtime, thus allowing you to take your app from one environment to another and simply change the XML as opposed to recompiling. Some devs don’t seem to care about this, but I find it quite useful and so do many of my clients so I created a simple DynamicServiceLocator (SL) class and “injected” it into my Example.
Tutorial Goal: Create a Dynamic Service Locator that Instantiates Services from an External XML File at Runtime
Assets
Acronyms
- PV = PassiveView
- SAS = Spring ActionScript
- SL = Service Locator
- VM = View Mediator
- CG = Cairngorm
- IoC = Inversion of Control
- DTO = Data Transfer Object
Caveats
Please read the previous posts in this series to get up to speed:
I won’t go into the details of what Swiz is and how it works as I’d like to let the code speak for itself and you can just hit up the Swiz homepage and view their simple examples; that said, I’ll point out several things I really like about Swiz.
I am using Swiz 0.6.4, as I found issues with the 1.0.0 alpha release (that I’m going to bring up with Ben Clinkenbeard).
Getting Started
The idea is simple:
- Create an XML file that defines all of the services leveraged in the app.
- Add the
DynamicServiceLocatorto theBeanLoader(Beans.mxml) and give it the URL to load the services XML file. - Listen to the
DynamicServiceLocator’s complete event, indicating that the services are loaded and parsed, and start the app up. - Inject the SL into the Delegates as opposed to injecting the services directly (that were previously defined and hardcoded in the
BeanLoaderfrom Part 1). - In the Delegate, request a service from the
DynamicServiceLocatorvia an ID that was set in the service’s definition in the XML file. - Use the service.
Let’s take it piece by piece…
Step 1: Create Services XML File
Let’s start by creating an XML file called services.xml and putting it in src/assets/service-locator/. Next, let’s add some services — truth be told, I took the format for the XML that defines a service directly from the idea behind the application context files used in SAS and then created my own Object Factory…it’s very simple mind you b/c I didn’t need a huge object factory taking up a ton of memory when the only objects it would ever create were 8 different services.
<?xml version="1.0" encoding="utf-8"?> <objects> <!-- =================================================================== --> <!-- SERVICE LOCATOR --> <!-- =================================================================== --> <!-- =================================================================== --> <!-- This application context defines multiple test configurations, it can be expanded to include a deployment configuration as well, please see comments below. 1. Local Testing: XML Services 2. Dev Server Testing: XML Services 3. Deployment: XML Services --> <!-- =================================================================== --> <!-- =================================================================== --> <!-- 1. LOCAL TESTING: XML Services --> <!-- =================================================================== --> <object id="loginService" class="mx.rpc.http.mxml.HTTPService"> <property name="url" value="assets/xml/login.xml" /> <property name="resultFormat" value="e4x" /> <property name="method" value="GET" /> </object> <object id="employeeService" class="mx.rpc.http.mxml.HTTPService"> <property name="url" value="assets/xml/employee_list.xml" /> <property name="resultFormat" value="e4x" /> <property name="method" value="GET" /> </object> </objects>
The idea is to create a list of services as object nodes. Start by giving each <object> node a unique id attribute, as this is the key we’ll use to request the service later. The following line defines the loginService:
<object id="loginService" class="mx.rpc.http.mxml.HTTPService">
Next, define the concrete Flex service type in the class attr — right now the service class must be one of the following types:
mx.rpc.http.mxml.HTTPServicemx.rpc.http.HTTPServicemx.rpc.remoting.mxml.RemoteObjectmx.rpc.remoting.RemoteObjectmx.rpc.soap.mxml.WebServicemx.rpc.soap.WebService
Finally, add any additional properties you want to define for the service as <property> nodes with the name and value attrs matching properties that are available for the service type. The following line adds the url property for the HTTPService:
<property name="url" value="assets/xml/login.xml" />
Step 2: Add DynamicServiceLocator to the BeanLoader
We’ll add the SL to the BeanLoader just like we did for all the other objects we want managed by the IoC Container, except we’ll also provide her with the URL to our services.xml file that we just created. When the url property is set on the SL, it will automatically load the services, parse the XML, instantiate a service for each <object> node it finds that matches one of the aforementioned service classes, and then adds that service to it’s hash or Dictionary object’s list of services via the id as it’s key.
<service:DynamicServiceLocator id="serviceLocator" eventDispatcher="{this.dispatcher}" url="assets/service-locator/services.xml" />
The other thing to make note of is that we’re again passing a reference to the BeanLoader’s dispatcher so the SL can broadcast events to other objects in the Swiz IoC Container.
Step 3: Listen to the SL’s Services Load Complete Event
Since this is more of an application level event, we’ll make the ApplicationController object handle this event:
[Mediate( event="serviceLocatorServicesLoadComplete" )]
public function onServiceLocatorLoadComplete():void
{
logger.debug("onServiceLocatorLoadComplete");
this.eventDispatcher.dispatchEvent(new DynamicEvent("showMainView"));
}
Which then in turn broadcasts a “showMainView” event that’s handled by the ApplicationViewMediator and ultimately removes a simple preloader (which is there in case the SL should take any significant amount of time to load and parse — unlikely here, but useful in the future) and adds the MainView to the stage.
[Mediate( event="showMainView" )]
public function showMainView():void
{
logger.debug("showMainView");
this.view.removeChild(this.view.progressBar);
this.view.addChild(new MainView());
}
Now this may seem like overkill for this extremely simple example, as I could have just listened to the “serviceLocatorServicesLoadComplete” in the ApplicationViewMediator, but I wanted to proxy the event through the ApplicationController in case more needed to be done…What if we wanted to update a model level var to indicate that the SL was complete? What if we wanted to tell other objects that the SL was complete?…we wouldn’t want to do that from the ApplicationViewMediator as it’s sole purpose is to lend a hand to the MainView UI component and not to orchestrate application level processing, work, etc. Again, overkill in this example, but I wanted to put it in here as an example of what you might want to do in a much larger application.
Step 4: Inject the SL into the Delegates
Next we’ll want to inject the SL into the Delegates as opposed to injecting the hardcoded services defined in the BeanLoader. First, let’s declare the new serviceLocator property for the Delegate:
[Autowire(bean="serviceLocator")] public var serviceLocator:DynamicServiceLocator;
Next, let’s remove the service var’s declaration and create a simple getter method for it instead:
public function get service():HTTPService
{
return this.serviceLocator.getMXMLHTTPService("loginService");
}
Step 5: Request Service From SL
This allows us to leave the actual use of the service property untouched in our service method calls - this code hasn’t changed from Part 1, but I’m showing it for clarity. We’ll look at the Login Delegate for example:
public function login(dto:LoginDTO):AsyncToken
{
logger.debug("login");
return this.service.send();
}
Wrapping Up
The only other thing that I failed to mention that’s different from Part 1 is the use of a simple ProgressBar component in the Application root to provide the user with some additional feedback while the SL is loading and parsing. If you look at the main application file EmpMgmtConsoleSwizPassiveView you’ll notice that the MainView is now replaced by the ProgressBar component — once the SL is complete the ProgressBar is removed from the stage and the MainView is added (as mentioned in Step 3).
Finally, to test out the dynamic SL, open up the services.xml file in the example source code and comment out the first set of services that point to local, project-level XML files and ucomment the services that point to the WASI server. See, no recompiling.
And that about does it. Any questions?
#1 by Aaron West - July 22nd, 2010 at 18:07
We’re using a ServiceLocator in a decent sized AIR application after reading this post. The pattern definitely helped us solve the problem of mock vs local vs staging vs production execution. Some developers simply aren’t involved in the server-side code creation, so they run the app in what we’re calling “mock” mode. This mode feeds the app with data from local XML files.
To maximize the benefit of a ServiceLocator we’ve defined 5 different XML files that match to each run mode: mock, local-only, staging, production and current. “Current” isn’t really a run mode, but instead is what the app uses each time it fires up. Separating these run modes from the current mode has given us the flexibility to run any service in any mode. One service can be fed from mock XML data while another can be fed from the local server-side system.
Or, we can copy all the XML from one of the files - mock.xml for instance - and paste the XML into current.xml setting all services to use mock data.
The only downside to this method that I can see is the additional workload it puts on the developers. Whenever we introduce a new service or a new method of a service we have to write the service handler at least twice. Once to retrieve and use mock data from XML and once to retrieve and use data from the server.
In my opinion the flexibility gained from ServiceLocators is worth the extra service programming.