Version 0.1 of my Cairngorm 3 / Parlsey application

February 18, 2011

These things are never done, but here’s my first cut at a Cairngorm 3 / Parsley application (view source enabled):

Some code and design ideas were borrowed from insync, others from cafe townsend.

It’s a rough cut, but not terrible. It uses Parsley model injection, commands and an event interceptor (when you try to place a bet and you’re not logged in, it prompts you to log in), Flex 4 skinning and is localizable. I added some navigation features, but think I’d need to use something like SWFAddress for deep-linking. It does not have unit testing, but I wrote it to be testable (with business logic in commands).

On my wish list:
– Deep linking through the URL.
– Using the Task Library so that after the user is successfully saved, the bet result event is sent.
– Unit testing.
– A coin flipping animation (which raises additional questions about how to synchronize events and view states)…

One way this deviates from the Cairngorm 3 guidelines is that it does not use the Presentation Model pattern. I decided against this for two reasons. First, the client I was learning this for does not use this pattern. Second, it seemed like an unnecessary layer of decoupling. If you use skins and the Presentation Model, the decoupling starts to get a bit ridiculous: for every view element you’d have a Presentation Model, a SkinnableComponent and a Skin, with the business logic contained in commands. Finally, there’s some data (such as the currently logged in user) that seems to belong in an application model rather than a presentation model – why tie it to a particular view component?

Events are separated into two categories: view events (which I prefix with “UI”) that are handled by top components, and application events which are handled by commands. For example, when the user clicks on a number to place a guess, a UIGuessANumberPickEvent is dispatched. Following the principle that only top-level components should dispatch application events, the GuessANumberPage component handles this event by dispatching the application event UserGuessANumberEvent. This is then handled by the dynamic command UserGuessANumberCommand. It’s kind of a lot of code for something so simple, but it follows a pattern, and allows for eventually testing the business logic.

There are two ways you can dispatch events so that the Parsley IOC can route them to commands. One way looks like this:

    [Event(name="guessFlipACoinWon", type="")]
    [Event(name="guessFlipACoinLost", type="")]
    public class UserGuessFlipACoinCommand extends EventDispatcher
        public function dispatchSave():void
            dispatchEvent(new SaveUserEvent(SaveUserEvent.SAVE, updatedUser.userVO));

The other way is this:

    public class UserGuessFlipACoinCommand
        public var dispatcher:Function;

        public function dispatchSave():void
            dispatcher(new SaveUserEvent(SaveUserEvent.SAVE, updatedUser.userVO));

I strongly prefer the second way. It’s more readable and vastly less brittle, since metadata has no error-checking.

There’s an architectural problem with this code that seems pretty endemic to the pattern itself: the LoginModel is essentially a global that can be injected anywhere, and the currentUser setter is public. This is not nearly as bad as making the currentUser a global variable, since at least there’s only one function that can modify the currentUser, but it’s not ideal. What I’d really like to do is have the setter only accessible to LoginCommand. One possibility would be something like this:

    public class LoginModel extends EventDispatcher
        private var _currentUser:User;

        // read-only bindable getter for the model's state
        public function get currentUser:User():String
            return _currentUser:User;

        public function setCurrentUser(value:user, command:LoginCommand):void
            if (_currentUser:User!= value)
                _currentUser:User= value;
                dispatchEvent(new Event("currentUserChanged"));

The idea is that the LoginCommand could set the current user by passing ‘this’ as the second parameter. Of course, anyone could call it like so: setCurrentUser(whateverUser, new LoginCommand()), so it’s not bulletproof. Also, it might complicate testing somewhat. Still, it would make for slightly more robust code.

Feedback welcome! Thanks.


8 Responses to “Version 0.1 of my Cairngorm 3 / Parlsey application”

  1. adrian said

    Hi mate,

    I enjoyed going through your code. I’m a fellow Flex Dev my self and currently using C3.

    Why haven’t you used the Cairngorm 3 ‘modules’ library, instead of Skins/Components which you said were borrowed from CafeTownsend sample ?

    • Hi Adrian,

      Thanks for your comment.

      Just to be clear, I didn’t borrow any SkinnableComponent/Skin code from CafeTownsend. All I did was use a similar structure, in that all visual components (including the main view) are separated out into SkinnableComponent and Skin subclasses. I wouldn’t have thought to do that on my own. Until I looked at CafeTownsend, I thought of SkinnableComponent as the base class for controls and the like, but wouldn’t have thought to use it for the main UI.

      One place my code differs from CafeTownsend is that it uses commands, where CafeTownsend calls its SkinnableComponent-subclasses “controllers” and puts business logic in there. Honestly, I think it’s much simpler and more readable, but less scalable and not easily unit testable.

      I did think of using modules for the games themselves, but that might be a version 0.2 thing. What would you see as the benefit to using modules in this application?

      – Scott

  2. adrian said

    Hi Scott,

    Dont’t worry about CafeTownsend, I think SkinnableComponent/Skin structure works really nice for your application.

    I personally don’t like the ‘controllers’ use in CafeTownsend, and agree with your commands approach which is more C3 orientated.

    Modules for next version sounds cool to me, I think generally for larger more complex applications it can bring a better mechanism for loading/rendering and you can develop/test in isolation, when working in a team of devs.

    I know for you casino app it is not needed and can work well without modules, but would be a nice to have, to better illustrate another guideline of Cairngorm 3.

    Anyway, the casino idea from that comedy film was wicked to use as a showcase of C3 🙂

    I will probably build one myself when I get some time, to illustrate some elements of C3 – maybe a ‘Renting-Helper’ to help tenants when looking for a new property – because that’s what I’m doing at the mo’ and went through my head a couple of times ‘why not build an app 🙂 ‘

  3. Sanford said

    I totally agree with this, though I hadn’t thought of it before: “I strongly prefer the second way. It’s more readable and vastly less brittle, since metadata has no error-checking.”

  4. Sanford said

    Follow-up on that: An advantage of using the [Event…] metadata tag is that it makes the Flash Builder code completion work for you, to fill out the “foo.addEventListener(” info. On the other other hand, you’ll only do that in unit tests so I still agree with you.

    • That makes sense. I was thinking that although the UserGuessANumberCommand class dispatches events, those events are directly routed to command objects by Parsley using the context file, so there was no need for it to be an EventDispatcher.

      But as it is, unit testing it might be a little tricky. You’d either have to use Parsley (so it would dynamically inject a message dispatcher function, ideally a custom one), or just set the dispatcher member variable to a function that would allow you to test that the right event was sent.


      Thanks for the feedback, Sanford!

  5. Sanford said

    Right. Setting that function to a test function would be easy. That’s what’s great about dependency injection. To test, you just set the properties.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: