Download - Parsley & Flex
Parsley is all about decoupling
Parsley is Open Source, licensed under the Apache License 2.0 Written by Jens Halm First version 1.0.RC1 (2007-12-09) Last version 3.0.0 (2012-02-06)
Overview
Parsley is an Application Framework for Flex and Flash Applications built upon an IOC Container and Messaging Framework that can be used to create highly decoupled architectures.
> 4 years
Features
Flexible IOC Container Dependency Injection Decoupled Bindings Messaging Framework Managed Commands Dynamic View Wiring Advanced Modularity Object Lifecycle Localization Extensibility
Parsley Spicelib
Command Framework XML-Mapper Reflection API Logging
Inversion of Control (IoC)
Hollywood Principle: Don't call us, we'll call you.
Program logic runs against abstractions such as callbacks
Inversion of Control (IoC)
IoC Example
package com { public interface IMap { //methods } }
package com { public class GPSNavigator implements INavigator { private var map:IMap; public function GPSNavigator() { this.map = new UkraineMapImpl(); } //other methods } }
Here are a common smells that should lead you to refactor to IoC
Here is the simplest possible IoC component
package com { public class GPSNavigator implements INavigator { private var map:IMap; public function GPSNavigator(map:IMap) { this.map = map; } //other methods } }
It is hard coded Not reusable.
package com { public class GPSNavigator implements INavigator { private static var map:IMap = MapFactory.getMap(); public function GPSNavigator() { } //other methods } }
Some other smells.
IOC Container
We may ask for the object from the container and the container creates the object and its dependencies
Parsley is a classic IOC Container. It provides support for Dependency Injection, Object Lifecycle Management and Messaging
EBookReader <interface>
File
PDFFileImpl TXTFileImpl
IoC Container
inject
creates
Dependency Injection (DI)
Dependency Injection (DI)
How objects obtain references to each other
The core feature of any IOC Container [Inject]
Dependency Injection (DI)
You may have these different tools
Dependency Injection (DI)
OR just one tool with attachments
You may have these different tools
Dependency Injection (DI)
This brings us to the Design Principles - Program to an interface, not an implementation
OR just one tool with attachments
You may have these different tools
Dependency Injection (DI)
Three ways of classic DI
Dependency Injection (DI)
public class Controller { private var model:Model; public function Controller(model:Model) { this.model = model; } }
Constructor Injection
Dependency Injection (DI)
public class Controller { private var model:Model; public function Controller(model:Model) { this.model = model; } }
public class Controller { private var _model:Model; public function set model(value:Model):void { _model = value; } }
Constructor Injection
Setter Injection
Dependency Injection (DI)
public class Controller { private var model:Model; public function Controller(model:Model) { this.model = model; } }
public class Controller { private var _model:Model; public function set model(value:Model):void { _model = value; } }
public interface IController { function injectModel(model:Model):void } public class Controller implements IController { private var model:Model; public function injectModel(model:Model):void { this.model = model; } }
Constructor Injection
Setter Injection
Interface Injection
public class Controller { private var model:Model; public function Controller() { model = new Model(); } } public class Controller { private var model:Model; public function Controller() { model = ModelFactory.create(Model); } } public class Controller { private var model:Model; public function Controller() { model = Model.getInstance(); } }
NOT Dependency Injection
IoC and DI Benefits
• Dependency management • Simplify the reuse of classes or components • Offers configuration flexibility • Simplify unit-testing • The "cleaner" code
Configuration and Initialization
Configuration and Initialization
1
• Telling the IOC Container which classes it should manage
• MXML, XML, AS
<?xml version="1.0" encoding="utf-8"?> <parsley:Objects xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:parsley="http://www.spicefactory.org/parsley"> // *hidden imports <fx:Declarations> <parsley:Object id="userModel" type="{Model}" lazy="true"/> <parsley:DynamicObject type="{Counter}"> <parsley:Property name="maxCount" value="100" /> </parsley:DynamicObject> <parsley:View type="{UsersView}"/> <service:MockUsersService /> <parsley:MapCommand type="{GetUsersCommand}" messageType="{GetUsersMessage}"/> </fx:Declarations> </parsley:Objects>
UserConfig.mxml
Configuration and Initialization
1
• Telling the IOC Container which classes it should manage
• MXML, XML, AS.
2
• Configure DI or Messaging for each individual class
• MXML, XML, Metadata tags
public class GetUsersCommand { [Inject] public var service:IUsersService; [Inject(id="userModel")] public var model:Model; public function execute():AsyncToken { return service.getUsers(); } public function result(users:ArrayCollection):void { model.users = users; } }
public class UsersView extends SkinnableComponent { [Inject] public var model:Model; [Publish(objectId="selectedUser")] [Bindable] public var seletedUser:User; [MessageHandler(selector="user")] public function externalUserSelection(msg:SelectMessage):void { //do something } }
GetUsersCommand.as
UserView.as
Configuration and Initialization
1
• Telling the IOC Container which classes it should manage.
• MXML, XML, AS.
2
• Configure DI or Messaging for each individual class
• MXML, XML, Metadata tags
3 • Initialize IOC Container
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:parsley="http://www.spicefactory.org/parsley" <fx:Declarations> <parsley:ContextBuilder config="{UserConfig}"/> </fx:Declarations> </s:Application>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:parsley="http://www.spicefactory.org/parsley" <fx:Declarations> <parsley:ContextBuilder> <parsley:FlexConfig type="{UserConfig}"/>
<parsley:FlexConfig type="{ServiceConfig}"/>
<parsley:FlexConfig type="{MockServiceConfig}"/>
</parsley:ContextBuilder
</fx:Declarations> </s:Application>
You can use one big configuration file
Or several files with separated configuration
ContextBuilder.newBuilder() .config(FlexConfig.forClass(UserConfig)) .config(FlexConfig.forClass(ServiceConfig)) .config(XmlConfig.forFile("logging.xml")) .build();
Configuration DSL
Decoupled Bindings [Publish] [Subscribe] [PublishSubscribe]
• This feature is much more dynamic than Dependency Injection. • It is really more the decoupled equivalent of the typical usage of Flex Bindings. • The published object does not even have to be a container managed object.
Decoupled Bindings
[Bindable] [Publish(objectId="selectedUser")] public var selectedUser:User; [Subscribe(scope="window")] public var selectedUser:User; [Bindable] [PublishSubscribe(persistent="true", objectId="selectedUserId")] public var selectedUserId:int;
<parsley:View type="{UsersView}"> <parsley:Publish property="selectedUser“ objectId="selectedUser"/> </parsley:View>
MXML Example
ActionScript Examples
uses Local SharedObjects
Messaging [ManagedEvents] [MessageDispatcher] [MessageHandler] [MessageBinding] [MessageError]
• The exchange messages between objects in a fully decoupled manner (sender and the receiver do not have to know each other)
• Messaging Framework is generic, it does not impose a particular usage style
[Event(name="userSelected",type="com.xyz.UserSelectedEvent")] [ManagedEvents("userSelected, somethingSelected")] public class UsersView extends SkinnableComponent { private function handleUserSelect(user:User):void { dispatchEvent(new UserSelectedEvent("userSelected", user)); } }
ActionScript Examples
public class UsersView extends SkinnableComponent { [MessageDispatcher] public var dispatcher:Function; private function handleUserSelect(user:User):void { dispatcher(new UserSeletedMessage(user)); } }
UserSelectedEvent.as Event based class
UserSeletedMessage.as Simple class
[MessageHandler(type="com.xyz.UserSelectedEvent", messageProperties="user, role“)] public function handleUserSelect(user:User, role:String):void { }
[MessageBinding(messageProperty="user", type="com.xyz.UserSelectedEvent")] public var user:User;
Object Lifecycle [Init] [Destroy]
preConfigure preInit postInit preDestroy postDestroy
Lifecycle phases
Object Lifecycle [Init] [Destroy]
[Init] public function init():void { // }
[Destroy] public function destroy():void { // }
The methods marked with [Init] get invoked after the object has been instantiated and all injections have been processed.
The methods marked with [Destroy] get invoked after the Context instance they belong to has been destroyed with Context.destroy() or when the object was removed from the Context.
Lifecycle phases
preConfigure preInit postInit preDestroy postDestroy
Managed Commands
• Dynamically add a command to a Context only for the time it executes • Declarative way to configure Commands • Grouping commands (parallel, sequence, flow) • Map command to message
Managed Commands <parsley:MapCommand type="{GetUserProfileCommand}"/>
public class GetUserProfileCommand { [Inject("userService")] public var service:RemoteObject; public function execute(msg:GetUserMessage):AsyncToken { return service.getUserProfile(msg.userId); } public function result(profile:Profile):void { // } }
[CommandResult] public function profileResult(profile:Profile, message:GetUserMessage):void { [CommandError] public function handleResult(fault:FaultEvent, trigger:GetUserMessage):void {
[CommandResult] [CommandError] [CommandComplete] [CommandStatus]
[Bindable] [CommandStatus(type="com.xyz.GetUserMessage")] public var isGettingProfile:Boolean;
<s:Button label="GetProfile" enabled="{isGettingProfile}" click="..." />
Managed Commands
<parsley:MapCommand messageType="{FindUserMessage}"> <parsley:CommandSequence> <parsley:Command type="{GetUsersCommand}"/> <parsley:Command type="{FindUserCommand}"/> </parsley:CommandSequence> </parsley:MapCommand>
Declaring Groups in MXML
<parsley:MapCommand messageType="{FindUserMessage}"> <parsley:ParallelCommands> <parsley:Command type="{FindUserCommand}"/> <parsley:Command type="{OutputDataComand}"/> </parsley:ParallelCommands> </parsley:MapCommand>
Building MVC Architectures
Custom Scopes
Custom Scopes
[ManagedEvents("add, remove", scope=“global")] [MessageDispatcher(scope="local")] public var dispatcher:Function; [MessageHandler(selector="add", scope="local")] public function save (event:UdpateUser):void { [Subscribe(objectId="selectedUser", scope="window")] public var selectedUser:User;
<parsley:ContextBuilder> <parsley:FlexConfig type="{UserConfig}"/> <parsley:FlexConfig type="{ServiceConfig}"/> <parsley:Scope name="window" inherited="true"/> <parsley:MessageSettings defaultReceiverScope="local"/> </parsley:ContextBuilder>
Default scope is global
Add new “window” scope
Change default scope to local.
Actionscript Examples
Localization
[ResourceBinding(bundle="resources", key="user.profile")] public var message:String;
<s:Label text="{resourceManager.getString('resources', ‘user.profile')}"/>
Usual using with flex binding
It is useful for properties of objects managed by the IOC Container
<parsley:View type="{ProfileView}"> <parsley:ResourceBinding property="header" bundle="resources" key="user.profile" /> </parsley:View>
MXML Example
Configuration Properties project.properties
dev_url = http://www.dev.xyz.com prod_url = http://www.prod.xyx.com version = 1.0.0
Property File compiled into the Application
<fx:Declarations> <fx:String id="properties" source="project.properties" /> </fx:Declarations> <parsley:ContextBuilder <parsley:PropertiesString source="{properties}"/> </parsley:ContextBuilder>
External Property File <parsley:ContextBuilder <parsley:PropertiesFile file="project.properties" /> </parsley:ContextBuilder>
<parsley:Object type="{Config}"> <parsley:Property name="dev" value="{properties.dev_url}" /> <parsley:Property name="prod" value="{properties.prod_url}" /> <parsley:Property name="version" value="{properties.version}" /> </parsley:Object>
Using Properties in MXML Configuration
Conclusion
Questions?
Information
Understanding