Accessibility

Table of Contents

Developing Flex RIAs with Cairngorm microarchitecture – Part 4: Feature-driven development

Service to Worker: listening for events with the front controller

In the Cairngorm Store, the features mentioned previously map to events such as getProducts, addProductToShoppingCart, deleteProductFromShoppingCart, filterProducts, sortProducts, and so forth. Whenever a user gesture indicates the desire to execute a feature, Cairngorm requires that you broadcast an appropriate event.

In response to each event, we have mentioned executing a feature. A common design pattern from the original Gang of Four pattern catalog is an excellent choice in such a scenario. In the Front Controller pattern, you implement features as classes, called commands. Each and every command exposes a single entry point, a method called execute(), which allows a third party to invoke the command without understanding what the command accomplishes. Often these commands are called worker classes because they are responsible for actually carrying out the work behind the application.

Understanding Command classes

Within Cairngorm, adding a new feature to the application typically involves adding a new Command class that can accomplish the work associated with the feature.

Consider one of the key items from the Cairngorm Store feature list—the ability to add products to a shopping cart. To implement this feature, you can create a new Command class called AddProductToShoppingCartCommand. The code for this class is as follows:

package com.adobe.cairngorm.samples.store.command
{
        import com.adobe.cairngorm.commands.ICommand;
        import com.adobe.cairngorm.control.CairngormEvent;
        import com.adobe.cairngorm.samples.store.model.ShopModelLocator;
        import com.adobe.cairngorm.samples.store.event.UpdateShoppingCartEvent;
        
        public class AddProductToShoppingCartCommand implements ICommand
        {
               public function execute( event : CairngormEvent ): void
               {
                       var shoppingEvent : UpdateShoppingCartEvent =
                               UpdateShoppingCartEvent( event );
                       
                       ShopModelLocator.getInstance().shoppingCart.addElement(
                               shoppingEvent.product, shoppingEvent.quantity);
               }
        }
} 

As you can see, it is not complicated. First of all, notice that the application-specific Command class implements the Cairngorm Command interface. If you were to view the source code for Cairngorm, you would see that this interface simply enforces that the command must have a method called execute() that acts as its entry point. This allows Cairngorm to execute each and every command without caring about what the command actually does.

If you look at the implementation of the execute() method, you can see how the event that caused Cairngorm to execute the command contains a ProductVO value object (shoppingEvent.product) and a quantity (shoppingEvent.quantity) in the data payload of the CairngormEvent class. The CairngormEvent class is also a Cairngorm class that gives each Cairngorm event a type (such as the addProduct type) and some associated data (such as the product and the product quantity to add to the shopping cart).

Because the shopping cart is a client-side state, it resides on the ShopModelLocator class. Therefore, the command has only to carry out the work that the user requests and add it to the appropriate amount for the product in the shopping cart, using the methods on the ShoppingCart class.

That's it! There's minimal work required to create a simple feature of the Command class. The command queries the event, extracting any data associated with the event so that it can perform its task. If executing the command affects the state of the application—such as requiring a new product to appear in the Shopping Cart view, for instance—the application accomplishes the task by updating the client-side state through ShopModelLocator. If you bind views correctly to ShopModelLocator, the user interface will reflect these state changes accordingly.

Creating helper classes in Cairngorm

There is a very important design point to emphasize here. In the preceding example, all complex business logic associated with what a shopping cart can and cannot do is encapsulated in a class called the ShoppingCart class. If a user adds a product to the cart, for instance, the ShoppingCart class adds it to the cart if it is not already in the cart. If the product already exists in the cart, the class increases the product's quantity.

Cairngorm does not relieve developers of the responsibility of creating their own business objects and classes on the client. Indeed, such classes make the application specific to its business domain.

Developers should always seek to extract these classes from the Cairngorm architecture. Pulling business logic out of commands and encapsulating into classes is a classic implementation of Extract class refactoring. The beauty of this technique is that you can perform extensive unit tests on these classes, document their APIs, and make them available for reuse to other application developers without any dependency on the surrounding Cairngorm application.

Managing commands with a controller

In the real world, having a large collection of workers with no one in charge is a recipe for chaos. Delegating responsibility to someone to take charge and control the workers from the front ensures that workers do their duty when they're required to do so.

In Cairngorm, the Front Controller pattern performs a similar function. Borrowing a design pattern from the J2EE community (but implementing it to respond to events on the client rather than HTTP requests on the server) Cairngorm introduces the Front Controller pattern as a single point of entry for all Cairngorm events.

Let's take a look at the Front Controller pattern from Cairngorm Store as an example:
package com.adobe.cairngorm.samples.store.control
{
        import com.adobe.cairngorm.control.FrontController;
        import com.adobe.cairngorm.samples.store.command.*
        import com.adobe.cairngorm.samples.store.event.UpdateShoppingCartEvent;
        import com.adobe.cairngorm.samples.store.event.FilterProductsEvent;
        import com.adobe.cairngorm.samples.store.event.GetProductsEvent;;
        import com.adobe.cairngorm.samples.store.event.SortProductsEvent;
        import com.adobe.cairngorm.samples.store.event.ValidateOrderEvent;
        import com.adobe.cairngorm.samples.store.event.ValidateCreditCardEvent;
        import com.adobe.cairngorm.samples.store.event.PurchaseCompleteEvent;
        
        public class ShopController extends FrontController
        {
               public function ShopController()
               {
                       initialiseCommands();
               }
               
               public function initialiseCommands() : void
               {
                       addCommand( GetProductsEvent.EVENT_GET_PRODUCTS, GetProductsCommand );   
                       addCommand( UpdateShoppingCartEvent.EVENT_ADD_PRODUCT_TO_SHOPPING_CART, AddProductToShoppingCartCommand );
                       addCommand( UpdateShoppingCartEvent.EVENT_DELETE_PRODUCT_FROM_SHOPPING_CART, DeleteProductFromShoppingCartCommand );         
                       addCommand( FilterProductsEvent.EVENT_FILTER_PRODUCTS, FilterProductsCommand );        
                       addCommand( SortProductsEvent.EVENT_SORT_PRODUCTS, SortProductsCommand );              
                       addCommand( ValidateOrderEvent.EVENT_VALIDATE_ORDER, ValidateOrderCommand );        
                       addCommand( ValidateCreditCardEvent.EVENT_VALIDATE_CREDIT_CARD, ValidateCreditCardCommand );             
                       addCommand( PurchaseCompleteEvent.EVENT_COMPLETE_PURCHASE, CompletePurchaseCommand );
               }       
        }
        
}

First, notice the code follows the best-practice approach of naming all events as constants. A common development error is to set up the controller to listen for an event, for example addProductToShoppingCart, but then, when broadcasting the event, mistakenly refer to it by another name, for example, addProduct. Consequently, you think you have broadcast a valid event and caused a Command class to execute; instead, FrontController refuses to invoke any commands and does not recognize the event name. These errors manifest at runtime only, as you are broadcasting events. In contrast, when you use static constants the compiler catches any mistyped events noisily at compile time, rather than failing silently at runtime. To catch such errors early, it is a best practice to use a static constant within each CairngormEvent class to identify the event within the FrontController. The addCommand statements in the code above are examples of this practice.

Next, notice how the constructor calls the initialiseCommands() method in the controller, ensuring that when the application does create the controller, it creates it with the full knowledge of which commands it can delegate work to, depending on which events it hears being broadcast.

Consider the example of adding products to the shopping cart. When the application broadcasts the UpdateShoppingCartEvent .EVENT_ADD_PRODUCT_TO_SHOPPING_CART command, the following entry in the controller ensures that the execute() method on the AddProductToShoppingCartCommand is invoked:

addCommand(
UpdateShoppingCartEvent
.EVENT_ADD_PRODUCT_TO_SHOPPING_CART, 
    AddProductToShoppingCartCommand
);

By extending the base FrontController class in the Cairngorm framework, you can use the addCommand() method of the FrontController class to register events with corresponding Command classes. The underlying Cairngorm architecture does the rest of the work. It simply broadcasts the appropriate event from anywhere in your application and Cairngorm ensures that the relevant command is invoked.

Also, notice how the Command classes are married to the events in the Front Controller via the addCommand method. There is no need to manually instantiate the Command class here, these "stateless commands", have the subtle benefit of ensuring a new instance of the Command is instantiated on the event call.

You create your controller in the main entry point for MXML, use the following code:

<control:ShopController id="controller" />

This is all you need to do to ensure that your application has a Front Controller pattern that responds to all the events and invokes all the commands that you registered with the addCommand() method.