Problem
We want to be able to execute commands in a sequence, where each command needs to be executed after the previous command has finished. This can be right after a call to execute() in case the command only implements ICommand, or after the invocation of the result() or fault() method, in case the command implements the IResponder interface and fetches some remote data.
The way to do this in Cairngorm is to let your command extend SequenceCommand - which acts as an abstract base class for commands that are executed in a sequence. Then you assign a value to the nextEvent property and call the executeNextCommand() method. This is done in the execute() method, the result() or fault() handler of the command. Each sequence command contains logic that says which command to execute next.
This has a major drawback though: since the sequence logic is coded in the commands themselves, it is very hard - not to say impossible without extra conditional logic - to reuse the command outside a sequence or in another sequence.
Solution
Instead of extending SequenceCommand and coding the sequence logic in the commands themselves, let's define a sequence of events outside of the commands. The commands themselves should not extend SequenceCommand. This will allow us to create "standalone" commands that have their specific task and then chain them into a variety of sequences.
Explanation
The solution Prana offers comes in the form of the EventSequence class. As said earlier, we want to have a non-intrusive way of defining sequences of events/commands without the need to code sequence logic in the commands. As an example, let's take an event that causes the user to log in, after which another event is dispatched to fetch the latest private messages for that user.
In Cairngorm we would have the following:
-
public function LoginCommand extends SequenceCommand implements IResponder {
-
-
// code left out...
-
-
public function result(data:Object):void {
-
var user:UserVO = data.result as UserVO;
-
ModelLocator.getInstance().user = user;
-
// here is the problem, the command knows what command to execute next
-
// we can't reuse this command in another chain
-
nextEvent = new LoadPrivateMessagesEvent(user.id);
-
executeNextCommand();
-
}
-
}
In the above code, the sequence logic in the result() handler prevents us from reusing the command outside of the sequence or in another sequence. This will most often result in duplicate code which makes the code base hard to maintain.
Looking at the flow of events and commands, we see that in most cases (but not all) a command will update some property in the ModelLocator. This is a good practice, because it keeps your commands consistent across your application: a command executes, proxies a remote call through a business delegate, adds itself as a responder to the remote call, updates the ModelLocator in the result() or fault() handler.
Example
Let's dive into an example of the EventSequence. I'll explain the different parts individually:
-
var sequence:EventSequence = new EventSequence();
-
-
// add a first event to the sequence which let's the user log in
-
sequence.addSequenceEvent(
-
LoginUserEvent,
-
[username, password]
-
);
-
-
// add a second event which fetches the latest private messages
-
sequence.addSequenceEvent(
-
LoadPrivateMessagesEvent,
-
[new Property(ModelLocator.getInstance, "user", "id")],
-
[new Property(ModelLocator.getInstance, "user")]
-
);
-
-
// start the sequence
-
sequence.dispatch();
Explanation
First thing we need to do of course is create a sequence. We can do this by creating an instance of EventSequence.
Next, the sequence expects us to define different events in it so that it knows when to fire which event. We do this by calling addSequenceEvent() on the sequence and passing in the properties of the event we want the sequence to dispatch.
-
sequence.addSequenceEvent(
-
LoginUserEvent,
-
[username, password]
-
);
The first argument is the class of the event. In our case this is LoginUserEvent with has 2 constructor parameters: username and password. These parameters are defined in the second argument of the addSequenceEvent() method as an array.
There are 2 options:
1. we pass in concrete values
2. we pass in an instance of Property which let's us define a lookup of a property (if you are familiar with BindingUtils, you will already know how this works)
The first option, passing in a concrete value, is quite straight forward. In case of the LoginUserEvent we pass in the credentials of the user, which will normally be fetched from the text inputs of the login form. So the definition of this could look as follows:
-
sequence.addSequenceEvent(
-
LoginUserEvent,
-
[username, password]
-
);
The second option is a property that will be evaluated or looked up at runtime and is defined by a host and a chain. In case of the second event, we want to pass in the user's id, but we can only do this if the user is defined in the ModelLocator and has a valid id (this will be after executing the LoginUserCommand). Doing the following wouldn't work (don't worry about the 3rd argument for now):
-
sequence.addSequenceEvent(
-
LoadPrivateMessagesEvent,
-
[ModelLocator.getInstance.user.id],
-
[new Property(ModelLocator.getInstance, "user")]
-
);
This will result in a runtime error (null pointer exception) because we want to pass in the concrete value of the user's id when we define the sequence. Since the ModelLocator does not contain a valid user instance at that point, this won't work. Therefor we need to define the value as a property of which the value will be "looked up" when we really need it (that is when firing the next event in our case).
The property defines a host: that is the variable that contains - or better will contain - the needed parameters for the event. In our case this is the model locator, so ModelLocator.getInstance(). The remaining arguments of the property instance are a chain of properties of the host defined as strings. So to get the user's id from the model locator we would define the following:
-
var p:Property = new Property(ModelLocator.getInstance(), "user", "id");
The 3rd argument of the addSequenceEvent() defines a trigger that will instruct the next event to be fired. In the example above, the next event will get fired when the user property of the model locator has changed.
After we have defined the sequence, all we need to do is call it's dispatch method. Also, the event/command mapping is still defined in the front controller, just like you would do without sequences. We do not need to specify any custom mapping.
Where to create sequences?
Normally I create a subclass of EventSequence and define the sequence in the constructor. I then instantiate the sequence in the appropriate view and dispatch it from there.
An alternative approach could be that the front controller has support for defining sequences. We would then have a centralized place for specifying these sequences instead of having them scattered in different classes. In this case there should also be a way of launching a sequence from the view. I don't have an answer for that right now, but I'll put some thinking into that.
What if the command does not update the model locator.
There will be times when a command does not update a property in the model locator. So we don't really have a point in time when we know we should trigger the next event. To solve this, the CairngormFrontController registers every executing command in a central registry called the PendingCommandRegistry. The command then has do decide when its action is finished and unregister itself:
-
public function result(data:Object):void {
-
// do some stuff
-
// ... now let the sequence know this command is done
-
// we do this indirectly by unregistering
-
PendingCommandRegistry.getInstance().unregister(this);
-
}
When calling unregister, the event sequence will get notified of this and trigger the next event if any. Note: this is not yet fully implemented in SVN and is more of a theory at the moment.
Conclusion
I personally think that this is a good approach to chaining events/commands. Especially because of the non-intrusive way this problem is handled. If you have any ideas or comments, feel free to leave them here or join the Prana list or Cairngorm list so we can discuss this.
Hope you enjoyed this and keep on coding!
Update 2007/10/12: added explanation of the triggers argument of addSequenceEvent()
Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
Christophe Herreman is a software developer living in Belgium. He's working on high-end Flex and AIR solutions at
October 16th, 2007 at 8:11 pm
We handled this problem at the command level too, in a way that requires no extra code to the commands, save for extending our SerialCommand class.
The SerialCommand class registers and unregisters itself automatically with a SerialCommandManager class.
SerialCommandManager handles the chaining of events, and even has logic to prioritize certain commands.
Let me know if you’re interested in examples.
October 23rd, 2007 at 7:32 pm
I’m not finding the EventSequence class in the distributions?
October 23rd, 2007 at 7:33 pm
Nick, I am interested in seeing your examples. Thanks!
October 23rd, 2007 at 10:00 pm
Hi,
there hasn’t been released a new version with the sequences classes. You can checkout the sources from SVN and add them to the classpath of your project. I’ll try to release a new version soon.
January 7th, 2008 at 5:58 am
In terms of where to create event sequences, usually events don’t contain references to the ModelLocator.
Is an EventSequence instance to be treated similar to an Event instance?
Should an event sequence be created in a command?
January 7th, 2008 at 6:02 pm
“PendingCommandRegistry.getInstance().unregister(this);”
- is this implemented? (the launch of trigger for next event) In 0.3.1 all I see is custom event dispatched, but is never used. The triggers for next commands cannot rely just on some model binding. The manual option to trigger next event (end of command execution) is extremely useful.
February 22nd, 2008 at 7:14 pm
Christophe - Do you have a working example project that uses this? I can’t find anything of the sort in the SVN…
February 23rd, 2008 at 7:00 am
Hi Douglas,
at this time there is no example available. I’ll put it on the TODO list for the next release.
If this helps, I added more documentation to the EventSequence class some days ago. http://prana.svn.sourceforge.net/viewvc/prana/trunk/main/src/org/pranaframework/cairngorm/EventSequence.as?view=markup
February 25th, 2008 at 3:55 pm
Thanks, Christophe. I’m happy to report that I found it pretty easy to understand and use EventSequence. I think that I downloaded it after you’d upgraded those comments…
March 5th, 2008 at 10:42 am
Hi Christophe,
I tried using this solution and observed a glitch.
I have a command that updates the ModelLocator, and I want to dispatch another command after it. The thing is that the variable in the ModelLocator is an int that has a default value (0) and if the command updates it with the same value, the next command in the EventSequence isn’t being dispatched. I guess it’s a BindingUtils glitch, but still…
March 5th, 2008 at 11:26 am
Hi Sefi,
thanks for reporting this. I’ll file a bug and look into it soon. Try checking the bug tracker on the sourceforge account for a resolution (if any) in the coming days.
April 9th, 2008 at 7:48 pm
Hi Christophe,
I have a related question. This works well for chaining commands, but what do you do if you need conditional logic?
For example, I’m working with synchronizing server & client data so I need logic that says “1) check the server to see if there’s new data and, *if* there is, 2) clear the model, 3) clear the local (SQLite) DB, 4) store the new data in the DB, then 5) recreate the model from the local DB”.
Obviously, several of these actions are asynchronous and will be implemented (in Cairngorm) with commands that use service delegates.
And it seems that (2)-(5) could easily be chained by subclassing EventSequence.
But where/how would you implement the “if” logic?
Douglas
April 10th, 2008 at 8:34 pm
Hi Douglas,
conditional event sequences would be a great feature! I think they could be implemented with a combination of the Property class we now have and some kind of Specification or Criteria implementation.
For now I would just set the second event up to listen for a change to a flag in the modellocator (e.g. newDataAvailable). That change would be a flag that is set only if their is new data detected on the server. If it is set (changed from “false” to “true”) then the sequence will continue. If there is no new data, then don’t update the flag in the result of your command. The sequence will hence never reach the second event and will not continue.
Does that make sense?
regards,
Christophe