After Cliff's suggestions, I went away and modelled the database app I am trying to write as a state machine. It has four states: an initial login screen, a main screen where you browse records in a grid, an editing mode where you can edit the current record, and a 'Do you want to save your edits?' modal dialog, which comes up when the user tries to do something destructive - eg log out - when there is an edited record.
Here is the model as rather grotty UML. My explanations will refer to it.
The current FSM library is very neat, and I admire in particular its XML-description feature. However, with all respect to Neil Manuell and Cliff, implementing the model above using the current FSM library is very difficult, and perhaps not possible.
However, with just a few changes it can be done. The following is my attempt to justify and champion them.
1. I realise that there are many alternative vocabularies for this sort of thing, but I claim that the current library makes rather unusual use of the term 'action'.
When a state machine performs a transition, the transition is (usually) initiated by a triggering event, and it it performs various actions as part of the transition.
The current library wrongly uses the term 'action' to describe the notifcation that initiates the transition, for example:
The transition from one state to the next is triggered by any actor sending a StateMachine.ACTION Notification - http://trac.puremvc.org/Utility_AS3_StateMachine/wiki/ReleaseNotes.
As supplied, this clash does not matter, because the library does not allow you to define actions that are associated with a transition - only the exit and entry actions. However, I say it does become necessary to be clear, and to make the distinction.
Therefore I propose the following change in terminology:
- The notification event that triggers a transition is renamed from 'Action' to 'Trigger'. ('Event' would perhaps be more standard terminology, but is confusing because of AS3's use of the term. 'Trigger' clashes with database use of the word, but I doubt this will cause any trouble.)
- The term 'action' is reclaimed for its more usual use in state transitions - ie something that is performed as part of a transition.
In the diagram above, triggers are shown in caps, the actions in camelcase, with a slash separating them. So going from the browsing to editing state by way of clicking the new record button is shown as NEW/createNewEntry meaning that the trigger NEW causes the action createNewEntry.
This is the terminology I will apply for the rest of this post.
In the library code I have changed StateMachine.ACTION to StateMachine.TRIG, plus various supporting changes.
2. In the current library, transition actions are not implemented. However as a designer one does miss them very much.
Consider the UML diagram above. If the user is editing a record (ie in the editing state), and performs a CANCEL, the grid display restores itself to how it was before. If the user presses APPLY, the grid reflects the edited record. Both transitions move between the same two states, but the actions taken are different.
Therefore it is not possible - or it is at least not easy - to perform the correct action by using entry and exit actions on the two transitions. (In my implementation, I perform a restoreAfterCancel on the CANCEL transition; obviously other designs are possible.)
You could get around this be introducing extra states, or adding tests in the exit and entry action (this seems to me to be particularly unsatisfactory approach, as you are moving logic out of the XML description of the FSM and into code).
After playing around with this and similar problems, I decided to add an optional action, associated with the transition. The action is performed after the exit from the original state and before the entry to the new. Part of the XML definition:
<state name="STATE_EDITING"To implement this I had to create a Transition class to store the action as well as the destination state.
3. Currently the Entry and Exit notifications pass the destination state in their Type field. I have not found this to be useful. You generally don't want to vary the behaviour of the code based on the destination state - this logic lives in the state machine itself.
Instead, I suggest that the Type field contains the trigger name. This enables me to handle the situation where the user clicks 'Log out' while editing a record. This causes the state machine to transition from editing to save_edit_screen in the diagram above. If the user clicks 'Yes - save the record' or 'No - scrap my edits', the state machine must then go on to do the logout. To do this, it needs to know the original trigger - which can be retrieved from Type.
I have also found it useful to pass the Body of the original trigger notification to the entry, exit, action and change notifications. This enables parametrised events. For example, the 'NEW' trigger allows the user to create different kinds of records (there are in fact half a dozen different New buttons).
4. The current implementation doesn't allow loopback transitions. (To be fair, without proper actions, there is no point, as the entry and exit actions should not fire.) I have found these useful, and have added them.
5. Using the FSM, I find the Application proxy is not needed. I originally took code from one of the PureMVC AS3 examples to implement a login. It used the application proxy to store in an int member variable whether the application was 'logged in' or not. This always struck me as a bit odd, because I would say that this is part of the business/application logic, rather than the domain logic - and therefore had no business [sorry] being in a proxy.
Using the FSM, I have been able to remove the Application proxy class. The FSM itself is a type of mediator, so can legitimately act as the seat of app logic.
In fact, as a general principle, I would say that using the FSM has given me a way to structure the 'business logic' of the whole application. I was finding that I was ending up with a sort of spaghetti notification app, with lots of parts responding to different notifications. I was losing my grip on the structure of the application as a whole. There was nowhere where one could view its high level structure.
I think the FSM supplies this missing feature, and allows me to keep an overview of things as I add features.
6. I am making extensive use of AsyncCommands. I use them to call all services on proxies, using Cliff's suggested AsyncToken mechanism from an earlier thread.
One consequence: I no longer need Proxies to send notifications, and in fact I have as a design choice outlawed the practice.
My reasoning is this: if one allows a proxy to send a notification, it needs to define the notification as a static constant in its own class (in order to avoid dependency on ApplicationFacade, or wherever you have defined you app's notification strings). But this means that you cannot have a single place where you can view all your global notifications - you have to go hunting through the domain proxies. This practice forces them all into one place.
I also use AsyncCommands to run modal dialogs - for example the one in the save_edit_screen state. When the user supplies the Yes/No/Cancel answer, the control is returned to the showSaveEditsDialog command that first displayed it. This command holds a little state, namely the transition that brought the machine to this state. It is able to use this to perform the next action. I couldn't see any other way of doing this without having (yuck!) some sort of global state.
I think this is a very handy and tidy little idiom - although it seems to be at odds with the FAQ: http://puremvc.org/content/view/80/188/
7. The action of a transition is generally to fire a single command. Where a notification causes more than one effect on mediators, I tend to make a new command that performs a sequence of notifications.
For example, when you log out, the app clears the login name from the top of the screen, clears the current record in the grid and displays the login dialog. These changes are performed jointly by three or four mediators on their respective components. Rather than have them all react to the 'global' notification 'LOGOUT', I have set up individual notifications for each one (CLEAR_LOGIN_NAME, CLEAR_CURRENT_RECORD, SHOW_LOGIN_SCREEN) which express the action in 'local' terms of the mediator concerned, rather than 'global' terms.
So the actions in the FSM tend to refer to 'global' change notifications, which map to commands. The commands then issue notifications enacted by mediators, where the notification constants are specific to mediators.
This got me thinking further. There are now a lot of constants declared in ApplicationFacade. Many of them refer to notifications only handled by one mediator. I was considering moving them out of ApplicationFacade, and into the mediator that deals with them. I would declare them using the
public static const SOME_NOTE:String = NAME+"someNote" ;trick to make sure they couldn't clash.
This would reduce the pollution in my 'global' ApplicationFacade namespace... but it would require the command classes to depend on the mediator concerned. The notification constants left in ApplicationFacade would refer only to commands, and to transition states.
Not sure about this...
Sorry, the above has been a somewhat muddled and rambling brain dump of my experiences using PureMVC with FSM. I do think I have some reasonable ideas, and have extended the library usefully. There are still things I think are missing:
- Guards. It would be handy to execute a guard before performing a transition. The guard could cancel the transition by sending a StateMachine.CANCEL notification. This would replace the present (in my view unsatisfactory) facility to cancel a transition from an exit action.
- Multiple actions. The ability to specify lists of actions to be executed. Like macro commands, but without the need to create an extra class.
PS: There is an excellent tool on SourceForge called the State Machine Compiler which generates state machines in a dozen or so languages, including C++, C#, PHP... but not AS3. Anyway, my ideas have been strongly influenced by the facilities it offers, and excellent documentation... nearly as good as Cliff's :-) Check it out here: http://smc.sourceforge.net/
PS#2: Oops: in the diagram the EDIT transition from editing to save_edit_screen is in the wrong place. It should be from browsing to editing. It is the transition that occurs when a user edits an existing record.