Home > Development > features
Last time we looked at Cairngorm's business delegates and services, and now we're going to see where Cairngorm commands fit in.
Commands and ControllersCairngorm commands implement the ICommand interface, which specifies an execute method. The command may also implement IResponder, which specifies a result and a fault method, to be called on result or fault from the service, as the case may be. Each command is registered with a controller, with the FrontController delegating the work to the controller (the Service-to-Worker pattern). Figure 19 shows the location of all the commands and controllers.
Table 11 and Table 12 list each command, how it does what it does, and what type of service is involved.
| Table 11. User section commands | |
|---|---|
| User section command | Discussion |
| FindAuthorsByName Command | On execute: Calls BookieDelegate.findAuthorsByName, which uses a DataService to fill the BookieModel’s author collection based on the event’s data, which is the search text for the author’s name. |
| FindBooks Command | On execute: Uses the event data, which should be a SearchEvent specifying the type of search and the search data, along with the string to search with from the user. On result: Sets the BookieModel’s books collection to the returned books. |
| FindSubjectsBy NameCommand | On execute: Calls BookieDelegate.findSubjectsByName, which uses a DataService to fill the BookieModel’s subject collection based on the event’s data, which is the search text for the subject. |
| GetAllAuthors Command | On execute: Calls BookieDelegate.getAllAuthors, which uses a DataService to fill the BookieModel’s author collection. |
| GetAllSubjects Command | On execute: Calls BookieDelegate.getAllSubjects, which uses a DataService to fill the BookieModel’s subject collection. |
| ReserveBooks Command | On execute: Creates a collection of reservations based on the event data, specifying books and a user, and calls BookieDelegate.reserveBooks, which uses a DataService to create and persist the reservations. These reservations should automatically be pushed to the admin clients viewing reservations. After the call, a notice is displayed, and then the user is logged out, making the assumption that there’s no longer a reason to use the application after reserving the books. |
| SignInCommand | On execute: Calls BookieDelegate.findPersonByCardNumber, which uses a RemoteObject service to look up the user based on the card number. On result: If the call returned a user, the user is stored on the model, the login screen is cleared, and a ViewLocator is used to change the view state of the screen to the main browsing view. If no user was found, an error message is displayed on the login screen. |
| SignOutCommand | On execute: The model is cleared of data by calling initialize, all the screens are looked up through ViewLocators and are cleared, and the main ViewLocator is used to return the view state to the login screen. |
| Table 12. Admin section commands | |
|---|---|
| xx | yy |
| Admin section command | Discussion |
| ClearReservations ReceivedCommand | On execute: Sets the AdminModel’s outstandingReservations to 0. This is the number of new reservations for display in the control bar in the Admin view as a notice to an administrator that may have been away from the computer when a new reservation came in. |
| DeleteBook Command | On execute: Calls the AdminDelegate.deleteBook method which deletes the book from the data service. |
| DeletePerson Command | On execute: Calls the AdminDelegate.deletePerson method which deletes the book from the data service. |
| DeleteReservations Command | On execute: Calls the AdminDelegate.deleteReservations method which deletes the set of reservations from the data service. |
| DeleteSubject Command | On execute: Calls the AdminDelegate.deleteSubject method which deletes the subject from the data service. |
| GetAllAuthors Command | On execute: Calls the AdminDelegate.getAllAuthors method which uses the PersonAssembler to fill a collection of people, limited to people with the author flag equaling true. |
| GetAllBooks Command | On execute: Calls the AdminDelegate.getAllBooks method which fills the model’s collection of books. |
| GetAllReservations AwaitingCheckOut Command | On execute: Calls the AdminDelegate.getAllReservationsAwaiting CheckOut method which uses the data service to fill a collection of reservations where the reservedOn is null, meaning that these reservations are awaiting checkout. |
| GetAllReservations CheckedOut Command | On execute: Calls the AdminDelegate.getAllReservations CheckedOut method which uses the data service to fill a collection of reservations where the reservedOn is a date, meaning that these reservations are already checked out. |
| GetAllSubjects Command | On execute: Calls the AdminDelegate.getAllSubjects method which fills the model’s collection of subjects. |
| GetAllUsers Command | On execute: Calls the AdminDelegate.getAllUsers method which uses the PersonAssembler to fill a collection of people with any of the people found. |
| ReservationNotification SubscribeCommand | On execute: Gets a reference from the ServiceLocator to the consumer that will receive messages from the JMS when new reservations are persisted. Sets the reservationConsumer property on the NotificationListener, which adds it as a listener for these types of messages. I will describe the NotificationListener in more detail later, but it’s responsible for popping up the notification window when a new reservation is persisted. |
| Reservation ReceivedCommand | On execute: Tells the NotificationListener to pop up the reservation notification message using a ViewLocator, and to increment the number of new reservations on the model, which should be shown on the control bar in the Admin view. |
Let’s look at the commands that call the methods we already looked at on the bookie delegate. First is the command to sign a user into the system, SignInCommand:
package lcds.examples.bookie.command {
...
public class SignInCommand implements ICommand, IResponder {
private var model:BookieModel = BookieModel.getInstance();
public function execute(event:CairngormEvent):void {
var evt:SignInEvent = event as SignInEvent;
var delegate:BookieDelegate = new BookieDelegate(this);
delegate.findPersonByCardNumber(evt.cardNumber);
}
...
Note that SignInCommand implements both ICommand and IResponder, which means that we expect this command to call a service asynchronously and get a call back to result or fault when the service is done. Asynchronously simply means that there’s no way for the Flash Player to call out to Java and wait until it’s done before going on with execution. You don’t even really want it to. Because Flash isn’t multithreaded, all execution in the Flash Player would be on hold until Java came back with an answer. When we call a method on a remote service we always specify a “callback,” or a method to call when it’s done. The Flash Player keeps track of that for you, but once you call out to the service layer, execution continues from the point you make the call and the method that’s making the call ends. In this case, the callbacks are those result and fault methods specified by the IResponder interface.
Here are the implementations in the SignInCommand:
public function result(result:Object):void {
if (result.result != null) {
var p:Person = Person(result.result);
model.user = p;
model.loginFailedMessage = "";
model.currentState = BookieModel.SIGNED_IN;
} else {
model.loginFailedMessage = "That card number was not found";
}
}
public function fault(fault:Object):void {
Alert.show("Unable to sign in", "Error while signing in");
}
The result method gets called back with a result object. On the result object is a property called result which contains the data from the callback. If you look all the way back through the delegate to the BookSearchService in Java, and further to the DAO, you’ll see that this call will return a Person object, which should be the user matched by the card number. If that person isn’t null, we know we matched a user with the card number, so we’ll let the user sign in (pretty secure, huh?). We’ll see how the login process works in more detail in a bit; this is just to let you see that the command gets a call back to result when a service call originating in execute returns. The fault method just pops up a simple alert to say there was a problem, but of course, that could be more complex as needed.
Let’s look at a command that calls a data service, GetAllSubjectsCommand:
package lcds.examples.bookie.command {
...
public class GetAllSubjectsCommand implements ICommand {
private var model:BookieModel = BookieModel.getInstance();
public function execute(event:CairngormEvent):void {
var delegate:BookieDelegate = new BookieDelegate();
delegate.getAllSubjects(model.subjects);
}
}
}
The GetAllSubjectsCommand only implements ICommand, so it doesn’t expect to be called back by the service after its execution is complete. And look at how simple the execution is too; all it needs to do is get the reference to the subject collection from the model and pass it to the delegate, which you’ll remember calls a fill on that collection.
The Command pattern is a smart way to keep execution of a discrete set of functionality modular and reusable. Anyplace in the code that needs to get all the subjects doesn’t need to know anything but to call the right command, and that command can be called anywhere in the application. If the process to get all the subjects into a collection changes none of the code that kicks off the command needs to know, so they’re insulated from change.
Next week we'll look at the Cairngorm model locator and how we'll use the power of Flex's binding to easily configure the view based on the state of the model. You can always find the entire series here.




Comments
Leave a comment