[Arp] Arp 3 Plans
Aral Balkan
aral at ariaware.com
Sat Jan 7 02:00:02 PST 2006
Hi all,
I mentioned that I had begun to think about Arp 3 in the OSFlash list. I
also fleshed out ideas and code for the next version of the framework
and wanted to share this with you for feedback, comments, etc. I've been
talking with Christophe about his work on extensions as well as Muse and
you will find elements of both in the plan below.
The most important goal, as always, is for Arp3 to simplify things even
further.
The plans below detail a system that will be as easy, if not easier to
use that Ruby on Rails for Flash/Flex/RIA development. I'm personally
really excited about its implications and look forward to your thoughts.
PS. Please note: None of the code below has been tested, it's a complete
brain-dump that I wrote into SePY.
/*
What is Arp?
Arp is a simple yet powerful structural framework for creating Rich
Internet Applications (RIAs) on the Flash Platform. Simplicity and DRY
(Don't Repeat Yourself) are Arp's core tenets. Arp aims to boost
developer productivity with code generation and introspection and
integration with major application servers and databases. Whenever
possible, Arp uses established Software Design Patterns. These include
core patterns such as Controller, Business Delegate and Value Object.
Arp favors convention over configuration -- this means that it uses
intelligent defaults and naming conventions whenever possible. The
ultimate aim is to bring the start-up time of a new RIA project close to
zero and encourage best practices, test-first development for RIAs on
the Flash Platform.
* * *
What's New in Arp 3?
Full-stack framework:
Creating and working with RIAs on the Flash Platform is now a piece of
cake with the integration of Ruby On Rails-type functionality.
Arp 3 supports cross-platform code generation developed in PHP [Using
Cake? CakeAMFPHP?], ActiveRecord-type database introspection, etc. for
quickly getting up and running with your RIAs. Server-side support for
the following languages is provided based on Flash Remoting:
* PHP (AMFPHP)
* J2EE (OpenAMF)
* Ruby (on Rails)
ActiveRecord-style introspection is supported for the following databases:
* MySQL
* PostgreSQL
DRY:
One of the core tenets of Arp is simplicity. Arp is even simpler in its
third incarnation and has gone DRY (Don't Repeat Yourself). This means
that your ARP3 applications will have less code and thus lower risk.
Convention over configuration:
As part of DRY, Arp 3 favors convention over configuration. This was
also a cornerstone of Arp 2, but we take it further here with the
introduction of Requests.
Requests:
When a form in the view needs to make a request that will require
business logic to be executed, it creates a new Request instance. A
request is a special event. It bundles the necessary data for the
request to be carried along with the request itself and specifies
optional success and failure handlers to be notified.
The success and failure handlers *only* carry out view logic. Unlike Arp
2, no data handling, storages, update, etc. is done there.
Custom Controllers are now optional:
For most applications you will not need to create custom controllers for
your application or for external forms. The new, DRY Arp's base
controller class, along with the introduction of Requests will handle
all the hard work for you.
Explicit Commands are now optional:
The new base Controller class and Requests make the explicit declaration
of Commands redundant for most cases. Of course, if you would rather
implement explicit Commands (eg. to use as part of a Memento pattern to
implement Undo/Redo), you can.
Business Delegates are now a central part of the framework:
At the very basic implementation, a Business Delegate includes a
reference to a remote service to which Requests will be proxied.
If you would rather carry out business logic on the client side in
response to a request, just implement the necessary method in the
Business Delegate and it will get called in place of the remote service
(if any).
In the most advanced, you can use a map to specify multiple remote
services for methods and mix in local method implementations too.
Data Binding and the Model:
The model is updated from the Business Delegate and data bindings on the
View [based on Christophe's code] (along with custom formatter
functions, etc.) carry out view updates automatically.
External Forms:
Easy workflow for using external forms and registering them and their
controllers with the main application.
Shared Libraries:
Supports transparent use of shared libraries.
Integrated unit testing:
Arp 3 includes the open source ASUnit unit testing framework and
advocates test-first development.
Integrated logging:
Arp 3 includes the open source LuminicBox logger for logging.
IDE support:
Support and documentation for creating Arp 3 projects is provided for
the following IDEs:
* Eclipse
* FlashDevelop <-- Do we need anything special? (No?)
* SePY <-- Do we need anything special? (No?)
* Others????
Compatibility:
Works with Flash, Flex 1.5 and Flex 2, ActionScript 2 (AS2) *and*
ActionScript 3 (AS3). Use the same workflow regardless of which
technology you're using. Applications based on Arp 3 are very easy to
port between these technologies. (Note: Since Flex 2 and AS3 are
currently in Alpha, these features may change in the future in line with
Adobe's code changes in future alphas, betas and releases.)
*/
//////////////////////////////////////////////////////////////////////
//
//
// Arp 3 Base Application Class
//
//
//////////////////////////////////////////////////////////////////////
class org.osflash.arp3.Application
{
//////////////////////////////////////////////////////////////////////
//
// Method: registerScreen()
//
// Registers an external screen with its Controller so that the
// Controller can listen for System Events on the screen and
// map them to Commands.
//
// Also calls a handler on the Application form so that it can
// register to listen for View Events.
//
//////////////////////////////////////////////////////////////////////
public function registerScreen ( screenName:String, screenRef:Object )
{
Log.info ("Registering screen: " + screenName);
//
// Register the screen with its Controller
//
var ControllerClass = findControllerClass( screenName );
// Remove the controller if it has already been created
// since if we go back to a screen, the screen refuses
// to display correctly unless this is done.
delete ControllerClass["inst"];
var screenController:Object = ControllerClass.getInstance();
screenController.registerScreen ( screenRef );
//
// Call handler on Application form. If you want to listen for
// View Events on the external screen, that's the place to do
// it. The handler is named onRegisterScreenameScreen
// (eg. onRegisterLoginScreen.)
//
var localHandler:Function = this [ "onRegister" + screenName +
"Screen" ];
localHandler.apply ( this, [ { target: screenRef } ] );
}
//////////////////////////////////////////////////////////////////////
//
// Method: unRegisterScreen()
//
// Removes the controller for the screen so that the screen can be
// re-created successfully in the future.
//
//////////////////////////////////////////////////////////////////////
public function unRegisterScreen ( screenName:String, screenRef:Object )
{
Log.info ("Un-registering screen: " + screenName);
var ControllerClass:Object = findControllerClass( screenName );
var screenController:Object = ControllerClass.getInstance();
// Remove the controller since the screen is being removed -
// this allows us to re-open popup windows without issue.
//screenController.unRegisterScreen ( screenRef );
delete ControllerClass["inst"];
var localHandler:Function = this [ "onUnRegister" + screenName +
"Screen" ];
localHandler.apply ( this, [ { target: screenRef } ] );
}
//////////////////////////////////////////////////////////////////////
//
// Group: Private methods
//
//////////////////////////////////////////////////////////////////////
private function findControllerClass( screenName:String ):Object
{
//
// The package string must be declared in the projectPackage
property.
// It can contain any number of packages. However, the
Controller class
// *must* be in a package called "control"
//
var packageExploded:Array = mProjectPackage.split(".");
var numPackages:Number = packageExploded.length;
var packageRef:Object = _global;
for ( var i:Number = 0; i < numPackages; i++ )
{
packageRef = packageRef [ packageExploded [ i ] ];
}
return packageRef["control"][ screenName + "Controller" ];
}
}
//////////////////////////////////////////////////////////////////////
//
//
// Arp 3 Base Controller Class
//
//
//////////////////////////////////////////////////////////////////////
class org.osflash.arp3.Controller
{
private var inst:Controller;
//////////////////////////////////////////////////////////////////////
//
// Private constructor - Singleton - Implement getInstance()
// abstract method in subclass.
//
//////////////////////////////////////////////////////////////////////
private function Controller ()
{
// DO NOT CALL WITH new(). Singleton.
}
//////////////////////////////////////////////////////////////////////
//
// Registers the view with the controller
//
//////////////////////////////////////////////////////////////////////
public function registerView ( view:Object )
{
mView = view;
registerRequests();
}
//////////////////////////////////////////////////////////////////////
//
// Registers to listen for requests on the view
//
//////////////////////////////////////////////////////////////////////
private function registerRequests()
{
for ( var i:String in mView )
{
if (i.substring(0, 7) == "request" )
{
mView.addEventListener ( i, this );
}
}
}
////////////////////////////////////////////////////////////////////////////
//
// Handle event instances
//
////////////////////////////////////////////////////////////////////////////
function handleEvent ( eventObj )
{
//
// handleEvent() is a generic event handler that gets called
whenever
// an event is heard.
//
// Get the name of the system request
var requestMethodName:String = eventObj.type;
// Strip the request name while staying compatible with Arp 2
(in which the
// system event would not have a "request" prefix.
requestMethodName = substring(requestMethodName, 0, 7) ==
"request" ? substring(requestMethodName, 7) : requestMethodName;
// Reference to the view for this request
var view:Object = eventObj.target;
// Reference to the business delegate
var requestBusinessDelegate:Function = eventObj.businessDelegate;
// Reference to the data
var data:Object = eventObj.data;
//
// Does an explicit Command exist to handle this request?
//
// Note: This behavior is different from Arp 2: You *will not*
get an error
// if you forget to add a command manually to your Controller.
//
var commandNameToCheck:String = requestMethodName + "Command";
if ( commands [ commandNameToCheck ] != undefined )
{
//
// Yes: Use the explicit command
//
// Create a new command
var theCommand = new commands [ commandNameToCheck ] ();
// Execute the command
theCommand.execute( view );
}
else
{
//
// No: Use automatic mapping to call the correct business
// method on the Business Delegate
//
requestBusinessDelegate [ requestMethodName ].apply (
requestBusinessDelegate, [ view, data ] );
}
}
////////////////////////////////////////////////////////////////////////////
//
// Singleton accessor method: If you override the base Controller
class,
// make sure your overwrite this static function with one that
generates
// an instance of your subclass.
//
////////////////////////////////////////////////////////////////////////////
public static function getInstance ()
{
if ( undefined == inst ) inst = new Controller();
return inst;
}
}
//////////////////////////////////////////////////////////////////////
//
//
// Arp 3 Base Business Delegate
//
//
//////////////////////////////////////////////////////////////////////
class org.osflash.arp3.BusinessDelegate
{
////////////////////////////////////////////////////////////////////////////
//
// Use the __resolve function to get notification of methods that don't
// exist. We try to handle these requests with automatic proxying to
// a server-side service if one is provided.
//
// For business methods that have client-side implementations, __resolve
// will *not* get called and thus they have highest precedence.
//
////////////////////////////////////////////////////////////////////////////
function __resolve ( functionName )
{
// Does a specific service mapping exist for this method?
// TODO
// Yes: Proxy the method call to the service and register
// the default success and failure handlers on the view
// TODO
// Does a global service declaration exist for this Business
Delegate?
// TODO
// Yes: Proxy the method call to the service and register
// the default success and failure handlers on the view
// TODO
// No: Raise an error as no explicit business method exists and
// neither does a remote service proxy.
}
}
/*********************************************************************/
//////////////////////////////////////////////////////////////////////
//
//
// Sample Application Class
//
//
//////////////////////////////////////////////////////////////////////
class com.somedomain.my.Application extends org.osflash.arp3.Form
implements org.osflash.arp3.IView
{
//
// Instance variables (members)
//
var mController:Controller;
//
// Declare requests.
//
// Controller subclasses will use these to automatically register
themselves to listen for requests.
// The naming convention states that request declarations must begin
with "request". The base Controller
// class uses for...in (AS2) and introspection (AS3) to map these.
//
var requestA;
var requestB;
var requestOtherOne;
//////////////////////////////////////////////////////////////////////
//
// Constructor.
//
// Do *not* place code here that relies on child components or forms.
//
//////////////////////////////////////////////////////////////////////
function Application()
{
// Nothing yet.
}
//////////////////////////////////////////////////////////////////////
//
// viewInit() - A common initialization method that will be called
// at onLoad() in Flash and onChildrenCreated() in Flex.
//
//////////////////////////////////////////////////////////////////////
function viewInit()
{
mController = ApplicationController.getInstance();
mController.registerView ( this );
}
//////////////////////////////////////////////////////////////////////
//
// Request Example
//
//////////////////////////////////////////////////////////////////////
// A View method
function someMethod()
{
// Get the value object
var valueObject = getValueObject();
// Make a system request: A request is really just an event by
any other name but uses a naming convention, has a specific use, bundles
necessary data and and has expectations associated with it.
var myRequest:Request = new Request ( "requestACommandOnModel",
valueObject );
}
//////////////////////////////////////////////////////////////////////
//
//
// Event Handlers
//
//
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
//
// Requests
//
//////////////////////////////////////////////////////////////////////
// Success
function requestASuccess ()
{
// Will be called if request A completes successfully
}
// Failure
function requestAFailure ()
{
// Will be called if request A fails
}
//////////////////////////////////////////////////////////////////////
//
// External Form Registrations
//
// To listen for events on external forms, create methods here using
the following naming convention:
//
// onRegister[Screename]Screen
// (eg. onRegisterLoginScreen is the place to add event listeners
for the Login screen. The method
// will be called when the external form is loaded, after its
controller has been registered.)
//
//////////////////////////////////////////////////////////////////////
//
// None
//
}
//
// THIS IS ACTUALLY NOT NECESSARY ==>
//
//////////////////////////////////////////////////////////////////////
//
//
// My Application's Controller Class
//
//
//////////////////////////////////////////////////////////////////////
class ApplicationController extends org.osflash.arp3.Controller
{
}
//
// <== THIS IS ACTUALLY NOT NECESSARY
//
//////////////////////////////////////////////////////////////////////
//
//
// A Sample Business Delegate Class on My Application
//
//
//////////////////////////////////////////////////////////////////////
class SomeBusinessDelegate
{
import org.osflash.arp3.services.IRemoteService;
import org.osflash.arp3.services.RemotingService;
import org.osflash.arp3.services.WebService;
import org.osflash.arp3.services.XmlService;
` //
// (Optional) If the remoteService variable is defined, it acts as the
// global remote service for this business delegate. If no remoteService
// is defined, the base Business Delegate class will check if a
// remoteServiceMap is defined and use the method to service mappings in
// that strucuture. If that doesn't exist either, you must implement
// all the business methods locally on the client.
//
// The precedence rules are as follows:
//
// Local business method -> Method defined in remoteServiceMap ->
global remote service
//
// Thus if a method is defined locally, it will override any custom
// mapping or global remote service mapping (ie., the local method will
// be executed.)
//
var remoteService:IRemoteService = RemotingService.getInstance();
//
// (Optional) If multiple methods have to use different remote services,
// you need to specify a remoteServiceMap. It is kept in this
// instance variable. The base Business Delegate class will call
// the createRemoteServiceMap() method to initialize this object.
//
var remoteServiceMap:Object;
//
// (Optional) Create Remote Service Map, to proxy different business
// methods to different services (this should be very useful when
// migrating from, say, a legacy LoadVars or XML-based system to
// Remoting, allowing incremental switchover on a per-method basis.)
//
var remotingService:RemotingService = RemotingService.getInstance();
var webService:WebService = WebService.getInstance();
var xmlService:XmlService = XmlService.getInstance();
function createRemoteServiceMap ()
{
methodTable =
{
remotingService:
[
"someMethod"
],
webService:
[
"aMethod", "anotherMethod", "yetAnotherMethod"
],
xmlService:
[
"anXmlServiceMethod"
]
};
}
//
// Local methods
//
function aLocalBusinessMethod ( parameters )
{
// Business logic
}
}
More information about the Arp
mailing list