Futurescale, Inc. PureMVC Home

The PureMVC Framework Code at the Speed of Thought


Over 10 years of community discussion and knowledge are maintained here as a read-only archive.

New discussions should be taken up in issues on the appropriate projects at https://github.com/PureMVC

Pages: [1]
Print
Author Topic: Transient proxies question  (Read 10069 times)
eliatlas
Jr. Member
**
Posts: 19


View Profile Email
« on: January 23, 2012, 10:31:34 »

I am dealing with the situation where I need to use a large number of transient proxies.
Something like 4-5.
My question is - shouldn't I make some custom proxy that will save information about all the states?
Logged
puremvc
Global Moderator
Hero Member
*****
Posts: 2871



View Profile WWW Email
« Reply #1 on: January 23, 2012, 10:55:11 »

Well, you could keep the transient proxy and just create a Value Object that holds the information. That's probably the better approach, because that Value Object can be passed into a view component that needs to know something about the application state. A custom Proxy subclass might only be useful to give you a typed getter that returns the data property cast to this VO. Unless you want the proxy to expose some management methods for manipulating values on the Value Object.

-=Cliff>
Logged
eliatlas
Jr. Member
**
Posts: 19


View Profile Email
« Reply #2 on: January 24, 2012, 01:55:15 »

Just to clarify.
Let's say that I have a number of asynchronous actions A,B,C,D,E,F.
In my application, I have a lot of places like:
:
after A ended
{
    if (B started)
    {
       start C;
    }
}
So I should create a transient Proxy that will hold AsyncActionsVO that will have public
properties for each state?
That means that after user's action and after some asynchronous action ends, I will make the
check  in the commands?
Logged
puremvc
Global Moderator
Hero Member
*****
Posts: 2871



View Profile WWW Email
« Reply #3 on: January 24, 2012, 11:07:36 »

Ok, at this point you really need to look into the StateMachine Utility.

Lots of transient Proxys and conditional code are not really the best way to implement complex state management. Your initial proposition was merely one of I can start my app 2 different ways and after performing an asynchronous operation triggered by either of them, I want to remember how I initially started. For that scope, using a transient Proxy made sense. And even adding a few other operations here and there, it's still a good way to hold dynamically generated data for various and sundry purposes.

But for the level of state management you're describing now, you need to formalize. And that's precisely what the StateMachine allows you to do. Using XML, you can create a formal definition of a Finite State Machine (FSM) which allows you to control the discrete states through which your application's thread of execution travels.

To illustrate, below is some real-world code from my Zarqon Desktop Control Center application. When it starts up, it has to do quite a lot of work involving asynchronous operations each of which can lead to various other states depending upon their outcomes.

It has to check to see if the app is connected to the internet, if there are application updates available, whether you're running a trial and if it has expired, or if you haven't run a trial but haven't entered a license. If you have a license, it needs to verify it with the one stored online, determine if it is still valid or  has expired. If you pass through all that, it has to check to see if you've entered your Amazon AWS keys (where your data is stored), and if so verify that they are valid with Amazon (or else prompt you for valid ones). Then it has to actually connect to your Amazon S3 buckets and get listings of your products and license holders. Then it sends you into the overview / welcome screen, from which you can go off into various other application states for managing products, licenses, email config, payment vendor config, etc.

Without the StateMachine, this application would be pretty hard to manage, even with all the benefits of MVC separation. You really need a big picture of all the possible states your app can be in. And that needs to translate from the whiteboard to the code, such that a developer diving into the project in 6 months or a year might be able to see the big picture even though the whiteboard has long been erased.

Since you probably aren't familiar with Zarqon (a product licensing app), you can look at the FSM below (unedited, straight from the source code) and get a good idea of what the application screen looks like in the various states by having a look at these two pages:

http://bit.ly/install-zarqon
http://bit.ly/configure-zarqon

Below, the FSM definition itself tells the story of the application's state far better than my wordy description above.

:
/*
 Zarqon - Active License Control System
 Copyright (c) 2009 Futurescale, Inc.
 Clifford Hall <clifford.hall@futurescale.com>
 */
package net.zarqon.controller
{
import net.zarqon.ApplicationFacade;
import net.zarqon.common.constants.ZarqonSays;

import org.puremvc.as3.multicore.interfaces.INotification;
import org.puremvc.as3.multicore.patterns.command.SimpleCommand;
import org.puremvc.as3.multicore.utilities.statemachine.FSMInjector;

/**
* Create and inject the StateMachine.
*/
public class InjectFSMCommand extends SimpleCommand
{
/**
* Configure and inject the Finite State Machine.
*/
override public function execute ( note:INotification ) : void
{
// Create the FSM definition
var fsm:XML =
<fsm initial={ZarqonSays.INTERNET_CHECKING}>

  <state name={ZarqonSays.INTERNET_CHECKING}>
      <transition action={ZarqonSays.INTERNET_ONLINE} target={ZarqonSays.UPDATE_CHECKING}/>
      <transition action={ZarqonSays.INTERNET_OFFLINE} target={ZarqonSays.INACTIVE}/>
  </state>

  <state name={ZarqonSays.INACTIVE}>
      <transition action={ZarqonSays.INTERNET_ONLINE} target={ZarqonSays.UPDATE_CHECKING}/>
  </state>

  <state name={ZarqonSays.UPDATE_CHECKING} changed={ZarqonSays.CHECK_FOR_UPDATE}>
      <transition action={ZarqonSays.UPDATE_PENDING} target={ZarqonSays.UPDATE_AVAILABLE}/>
      <transition action={ZarqonSays.NO_UPDATE_PENDING} target={ZarqonSays.ZQN_LICENSE_CHECKING}/>
  </state>

  <state name={ZarqonSays.UPDATE_AVAILABLE}>
      <transition action={ZarqonSays.SKIP_UPDATE} target={ZarqonSays.ZQN_LICENSE_CHECKING}/>
      <transition action={ZarqonSays.DOWNLOAD_UPDATE} target={ZarqonSays.UPDATE_DOWNLOADING}/>
  </state>

  <state name={ZarqonSays.UPDATE_DOWNLOADING}>
      <transition action={ZarqonSays.UPDATE_ERROR} target={ZarqonSays.UPDATE_FAILED}/>
      <transition action={ZarqonSays.UPDATE_COMPLETE} target={ZarqonSays.UPDATE_INSTALLING}/>
  </state>

  <state name={ZarqonSays.UPDATE_INSTALLING}>
      <transition action={ZarqonSays.UPDATE_ERROR} target={ZarqonSays.UPDATE_FAILED}/>
  </state>

  <state name={ZarqonSays.UPDATE_FAILED}/>

  <state name={ZarqonSays.ZQN_LICENSE_CHECKING} changed={ZarqonSays.CHECK_ZQN_LICENSE}>
      <transition action={ZarqonSays.ZQN_LICENSE_INVALID} target={ZarqonSays.ZQN_LICENSE_ENTRY}/>
      <transition action={ZarqonSays.ZQN_LICENSE_VALID} target={ZarqonSays.AWS_CREDS_CHECKING}/>
      <transition action={ZarqonSays.ZQN_NO_LICENSE} target={ZarqonSays.ZQN_REGISTER_OR_TRY}/>
  </state>

  <state name={ZarqonSays.ZQN_REGISTER_OR_TRY}>
      <transition action={ZarqonSays.ZQN_REGISTER} target={ZarqonSays.ZQN_LICENSE_ENTRY}/>
      <transition action={ZarqonSays.ZQN_TRY} target={ZarqonSays.AWS_CREDS_CHECKING}/>
  </state>

  <state name={ZarqonSays.ZQN_LICENSE_ENTRY}>
      <transition action={ZarqonSays.ZQN_LICENSE_SAVE} target={ZarqonSays.ZQN_LICENSE_SAVING}/>
  </state>

  <state name={ZarqonSays.ZQN_LICENSE_SAVING} changed={ZarqonSays.SAVE_ZQN_LICENSE}>
      <transition action={ZarqonSays.ZQN_LICENSE_SAVED} target={ZarqonSays.ZQN_LICENSE_CHECKING}/>
  </state>

  <state name={ZarqonSays.AWS_CREDS_CHECKING} changed={ZarqonSays.CHECK_AWS_CREDS}>
      <transition action={ZarqonSays.AWS_CREDS_INVALID} target={ZarqonSays.AWS_CREDS_ENTRY}/>
      <transition action={ZarqonSays.AWS_CREDS_VALID} target={ZarqonSays.ZQN_DATA_CONNECTING}/>
  </state>

  <state name={ZarqonSays.AWS_CREDS_ENTRY} >
      <transition action={ZarqonSays.AWS_CREDS_SAVE} target={ZarqonSays.AWS_CREDS_SAVING}/>
  </state>

  <state name={ZarqonSays.AWS_CREDS_SAVING} changed={ZarqonSays.SAVE_AWS_CREDS}>
      <transition action={ZarqonSays.AWS_CREDS_SAVED} target={ZarqonSays.AWS_CREDS_CHECKING}/>
  </state>

  <state name={ZarqonSays.ZQN_DATA_CONNECTING} changed={ZarqonSays.CONNECT_ZQN_DATA}>
      <transition action={ZarqonSays.ZQN_DATA_CONNECTED} target={ZarqonSays.OVERVIEW_SECTION}/>
  </state>

  <state name={ZarqonSays.OVERVIEW_SECTION} exiting={ZarqonSays.DETECT_DISABLED}
   changed={ZarqonSays.SHOW_OVERVIEW}>
      <transition action={ZarqonSays.LICENSE_MAINT} target={ZarqonSays.LICENSE_SECTION}/>
      <transition action={ZarqonSays.PRODUCT_MAINT} target={ZarqonSays.PRODUCT_SECTION}/>
      <transition action={ZarqonSays.CONF_EMAIL} target={ZarqonSays.EMAIL_CONFIG_ENTRY}/>
  </state>

  <state name={ZarqonSays.PRODUCT_SECTION} changed={ZarqonSays.SHOW_PRODUCTS}>
      <transition action={ZarqonSays.OVERVIEW_DISPLAY} target={ZarqonSays.OVERVIEW_SECTION}/>
      <transition action={ZarqonSays.LICENSE_MAINT} target={ZarqonSays.LICENSE_SECTION}/>
      <transition action={ZarqonSays.PRODUCT_MAINT} target={ZarqonSays.PRODUCT_SECTION}/>
      <transition action={ZarqonSays.CONF_AD_VENDOR} target={ZarqonSays.AD_VENDOR_ENTRY}/>
      <transition action={ZarqonSays.PRODUCT_SAVE} target={ZarqonSays.PRODUCT_SAVING}/>
  </state>

  <state name={ZarqonSays.EMAIL_CONFIG_ENTRY} changed={ZarqonSays.ENTER_EMAIL_CONFIG} >
      <transition action={ZarqonSays.EMAIL_CONFIG_SAVE} target={ZarqonSays.EMAIL_CONFIG_SAVING}/>
      <transition action={ZarqonSays.EMAIL_CONFIG_CANCEL} target={ZarqonSays.OVERVIEW_SECTION}/>
  </state>

  <state name={ZarqonSays.EMAIL_CONFIG_SAVING} changed={ZarqonSays.SAVE_EMAIL_CONFIG}>
      <transition action={ZarqonSays.EMAIL_CONFIG_SAVED} target={ZarqonSays.OVERVIEW_SECTION}/>
  </state>

  <state name={ZarqonSays.AD_VENDOR_ENTRY} changed={ZarqonSays.ENTER_AD_VENDOR} >
      <transition action={ZarqonSays.AD_VENDOR_SAVE} target={ZarqonSays.AD_VENDOR_SAVING}/>
      <transition action={ZarqonSays.AD_VENDOR_CANCEL} target={ZarqonSays.PRODUCT_SECTION}/>
  </state>

  <state name={ZarqonSays.AD_VENDOR_SAVING} changed={ZarqonSays.SAVE_AD_VENDOR}>
      <transition action={ZarqonSays.AD_VENDOR_SAVED} target={ZarqonSays.PRODUCT_SECTION}/>
  </state>

  <state name={ZarqonSays.LICENSE_SECTION} changed={ZarqonSays.SHOW_LICENSES}>
      <transition action={ZarqonSays.OVERVIEW_DISPLAY} target={ZarqonSays.OVERVIEW_SECTION}/>
      <transition action={ZarqonSays.LICENSE_MAINT} target={ZarqonSays.LICENSE_SECTION}/>
      <transition action={ZarqonSays.PRODUCT_MAINT} target={ZarqonSays.PRODUCT_SECTION}/>
      <transition action={ZarqonSays.CONF_PAY_VENDOR} target={ZarqonSays.PAY_VENDOR_ENTRY}/>
  </state>

  <state name={ZarqonSays.PAY_VENDOR_ENTRY} changed={ZarqonSays.ENTER_PAY_VENDOR}>
      <transition action={ZarqonSays.PAY_VENDOR_SAVE} target={ZarqonSays.PAY_VENDOR_SAVING}/>
      <transition action={ZarqonSays.PAY_VENDOR_CANCEL} target={ZarqonSays.LICENSE_SECTION}/>
  </state>

  <state name={ZarqonSays.PAY_VENDOR_SAVING} changed={ZarqonSays.SAVE_PAY_VENDOR}>
      <transition action={ZarqonSays.PAY_VENDOR_SAVED} target={ZarqonSays.LICENSE_SECTION}/>
  </state>
</fsm>;

// Create and inject the StateMachine
var injector:FSMInjector = new FSMInjector( fsm );
injector.initializeNotifier(this.multitonKey);
injector.inject();
}
}
}

As may be obvious, each defined state has some number of transitions, which define actions (notifications) which can trigger a change to a different 'target' state. For instance, lets look at the internet connectivity check.

Since it is an AIR app that must have a connection in order to perform its function, we check for the net before anything else (the initial state of the FSM is set to INTERNET_CHECKING). We go into an INACTIVE state if the check tells us we're offline (INTERNET_OFFLINE). But that connection continues to be polled, and whenever the connection is restored (i.e. the StateMachine receives an INTERNET_ONLINE action notification), we can exit the INACTIVE state and move on to the state where we check for updates (UPDATE_CHECKING):

:
<fsm initial={ZarqonSays.INTERNET_CHECKING}>
    <state name={ZarqonSays.INTERNET_CHECKING}>
        <transition action={ZarqonSays.INTERNET_ONLINE} target={ZarqonSays.UPDATE_CHECKING}/>
        <transition action={ZarqonSays.INTERNET_OFFLINE} target={ZarqonSays.INACTIVE}/>
    </state>

    <state name={ZarqonSays.INACTIVE}>
        <transition action={ZarqonSays.INTERNET_ONLINE} target={ZarqonSays.UPDATE_CHECKING}/>
    </state>
        .
        .
        .

As you described it, your code has all these conditionals that check various values to try and assess the current state. This is the typical brute force approach that arises in any complex application regardless of framework. The notion that you figure out the state when you need to know it by looking at various flags and properties. However, that random accumulation of state information makes it increasingly difficult to say what the state is, and you have all sorts of housekeeping to do managing those values if states can be re-entrant.

That is completely different from the FSM approach, where you have a very concrete picture of your application states at the heart of your design.

The FSM approach works as a sort of 'running status', where the StateMachine listens for actions and if one triggers a valid state transition, it sends out a notification. Commands and Mediators listen for these notes from the StateMachine, and act accordingly. A piece of code never has to figure out what the state is because it can only be invoked as a result of actually being in the proper state for its execution in the first place. This eliminates all those nasty conditionals and fuzzy notions about the overall state.

And also, there are some extra notes that go out saying that we're about to leave the current state (we can cancel that if some exit guard code in a Command or Mediator decides we're not ready to leave the state), and that we're about to enter another state (which we can also cancel from an entrance guard if we're not allowed or ready to enter the target state). So-called 'guard' code is where your conditionals go, and are much simpler, for instance, making sure your form is fully filled out before allowing it to be submitted, or only allowing access to manager functions if the user has the appropriate permissions).

So, you can see that the conditionals are now relegated to guarding against improper change of state and no longer used to actually determine the state it self.

State Machine Overview Presentation
http://puremvc.tv/#P003/

AS3 StateMachine Utility
http://trac.puremvc.org/Utility_AS3_StateMachine

Hope this helps,
-=Cliff>
« Last Edit: January 24, 2012, 12:40:43 by puremvc » Logged
Pages: [1]
Print