Home > ActionScript, Cairngorm, Cairngorm Confessions > Cairngorm Confessions: The Journey

Cairngorm Confessions: The Journey

December 28th, 2008

Introduction
Ever felt that there was something wrong with the way you architected your application with the Cairngorm framework?
Well, I for sure have.

Motivation
The motivation for this Flexback Flashback blog-post about my way with Flex and Cairngorm derives from to concurrent events; I’m currently working as a consultant with some great Java and .NET guys. In this project we have decided to use Cairngorm as our architectural framework, a decision I of cause is very happy about. In order to provide the team with an introduction to Cairngorm, I conducted a sample application to support my introduction. This process of going over the various points and parts of the framework led my thoughts back to my own path of learning and using Flex and Cairngorm (which I guess I’m still doing, and hopefully will continue to do). Simultaneously I had to present to a local Adobe RIA User Group meeting, and happened to get the same topic; “Cairngorm”. This concentration of going over Cairngorm and ways to apply it, cerated an overflow of thoughts and memories which is what this post is about. I’ll in the following try to show and tell about how I used Cairngorm, at different steps and level of understanding.

The example domain
In use for my drive down memory lane I have created a business domain called “Cyber Wallet”. The business of Cyber Wallet is an online wallet application in which you can withdraw and deposit money. When ever you do either of the two you create a transaction with a time stamp associated with the money involved. A quick sketch of the Domain Model looks like this:

So this being the simple Domain Model, we can continue on with the Use Cases in the Cyber Wallet application. I have narrowed them down to:

  1. Log In
  2. Log Out
  3. Deposit Money
  4. Withdraw Money

So we’ll take a look at some of these Domain Classes and the Use Cases implemented in various ways. Common for all the examples are that there is 3 main view parts:

  1. a Log In Panel, rendering what is related to authentication use cases (LogInPanel.mxml)
  2. a Wallet Panel, rendering what is related to the wallet use cases (deposit and withdraw) (WalletPanel.mxml)
  3. a Status Panel, rendering what is related to the running transactions related to the wallet use cases (StatusPanel.mxml)

The Money and the Transaction classes will be the same trough out the examples and are implemented as the following:

Money Class

?View Code ACTIONSCRIPT
package model.domain
{
	public class Money
	{
		private var _amount:Number;
 
		private var _currency:String;
 
		public function Money(amount:Number,currency:String)
		{
			_amount = amount;
			_currency = currency;
		}
 
		public function get amount():Number
		{
			return _amount;
		}
		public function get currency():String
		{
			return _currency;
		}
 
		public function toString():String
		{
			return _amount+ " "+_currency;
		}
 
		public function add(money:Money):Money
		{
			//TODO implement logic for adding money
			return new Money(_amount+money.amount,_currency);
		}
		public function substract(money:Money):Money
		{
			//TODO implement logic for subtracting money
			return new Money(_amount-money.amount,_currency);
		}
		public function equals(money:Money):Boolean
		{
			//TODO implement logic to check for different currencies
			return _amount == money.amount;
		}
 
	}
}

Transaction Class

?View Code ACTIONSCRIPT
package model.domain
{
	public class Transaction
	{
		public static const DEPOSITE:String ="deposite";
		public static const WITHDRAW:String ="withdraw";
 
		private var _type:String;
		private var _timeStamp:Date;
		private var _transactionNote:String;
		private var _money:Money;
 
		public function Transaction(type:String,money:Money,notes:String)
		{
			_type = type;
			_money = money;
			_transactionNote = notes;
			_timeStamp = new Date();
		}
 
		public function toString():String
		{
			return "START Transaction: ->"+_type.toUpperCase()+"\n\tDate: "+_timeStamp.toString()+"\n\tAmount:"+_money.toString()+"\n\t" +
					""+_transactionNote+"\nEND Transaction\n--------------------------------------\n";
		}
 
	}
}

Perspective
To put things in perspective I’ll have to mention that I started out with developing larger applications (larger than the “hello world” app) in Flex at the same time that I started looking in to Cairngorm. So my experiences of one or the other could be an effect of the synergy at place when getting your hands to dirty at once.

I have always understood “Model” in the Model-View-Controller pattern to be equivalent to a Domain Model. This Domain-centric Model understanding really troubled my mind when I started looking in to Cairngorm. I felt that the best practices of Cairngorm violated my best practices in terms of having, and working with, real Domain concepts and models. Later I have learned that “Model” in MVC to some is understood as a Service Layer, Transaction Layer or simply a Domain Representation of the Domain Model (but not the actual Domain instance(s)). When looking back on how I at first tried to make Cairngorm work with my actual Domain layer, I clearly see a mixing of clean Domain Models and Domain Representations.

Feeling of something being wrong
First time I got a sign of something being wrong was when I for the first time placed a [Bindable] tag inside a Domain Model object. Coming from the pure AS3 world where my Domain objects always had been nice and clean associated with one another as the would be reflected in the actual business domain, the whole concept of placing a “magic” tag on properties which other objects could bind to, felt wrong. Not the tag, and what the tag in isolation stood for (Broadcasting notification about property changes as any Observer pattern implementation), but what I could see would -, and soon did follow this way of binding view and model together (also; I was starting out in Flex, so messing my domain up, and making them useless for reuse in pure AS3 applications (what they would be, when depending on the [Bindable] tag) also felt bad - now I know I’ll never go back to pure AS3 though.. but I guess I needed the safety net if this “Flex thingie” didn’t turn out to be all that anyway :) ).

What I experienced was that I started composing my models, besides being a reflection of the business domain, also being a reflection of what the view needed. For instance say I was building an application with a use case of a user logging ind, I would consider a User Domain Model object, with data related to the conceptual user. - but building the View (which I understood should bind to the model, hence my User) I would start saying “what do I need in the view to make it run bound to the model? Oh well, I need some state information about whether the user is; logging in, logged in, logging out or logged out”. And I would go about either having a string property on the User object, reflecting these states (which I didn’t like all that much) or build a supporting model like “AuthenticationManager” (early interpretation of my knowledge of a Presentation Model) merely just having the states of the logging in use case. In terms of the problems with the states I kind of felt that I was still within boundary of good practice, excusing my self; “most objects have states, also a user object, right!?!?”. - though I knew that my interpretation of states in an object was much different than just a property holding a string.

Example 1
So given the above, I would properly go about creating my Wallet class like this:

Wallet Class

?View Code ACTIONSCRIPT
package model
{
	import mx.collections.ArrayCollection;
 
	public class Wallet
	{
		public static const STATE_IN_TRANSACTION:String = "stateInTransaction";
		public static const STATE_IDLE:String ="stateIdle";
 
 
		[Bindable]
		public var balance:Money;
 
		private var transactionLog:ArrayCollection;
 
		[Bindable]
		public var transactionNotes:String = "";
 
		[Bindable]
		public var state:String = STATE_IDLE;
 
		public function Wallet()
		{
			balance = new Money(0,"dollars");
			transactionLog = new ArrayCollection();
		}
 
		public function deposite(money:Money,notes:String):void
		{
			balance = balance.add(money);
			var trans:Transaction = new Transaction(Transaction.DEPOSITE,money,notes);
			transactionLog.addItem(trans);
			transactionNotes+=trans.toString();
		}
 
		public function withdraw(money:Money,notes:String):void
		{
			balance = balance.substract(money);
			var trans:Transaction = new Transaction(Transaction.WITHDRAW,money,notes);
			transactionLog.addItem(trans);
			transactionNotes+=trans.toString();
		}
	}
}

Notice the [Bindable] tags on the balance and the transactionNotes properties. Beside these Domain related properties a state property and corresponding state constants has been declared as well. These would be used for the view to change to the appropriate View state, when the wallet changed.
So a lot of View specific properties not really related to the Domain implementation of the Wallet.

When creating the WalletPanel in the application which would have the responsibility of render the amount of the Wallet to the screen, and providing controls for depositing and withdrawing money, we can now directly bind to the balance property of the Wallet.

WalletPanel View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%">
<mx:Script>
    <![CDATA[
        import event.WithdrawEvent;
        import event.DepositeEvent;
        import model.ModelLocator;
 
        //No dependency injection, but directly accessing the ModelLocator
        [Bindable]
        private var _model:ModelLocator = ModelLocator.getInstance();
    ]]>
</mx:Script>
    <mx:VBox width="100%" height="100%" horizontalAlign="center" verticalAlign="middle">
 
    <!-- Directly look up, in the model locator, 
        ... to access the wallet's balance's amount and currency
         ... NOT a good approach for dynamic and reusable views -->
 
        <mx:Label text="Wallet Balance is {_model.wallet.balance.amount +' '+ _model.wallet.balance.currency}" fontSize="30" />
        <mx:HBox width="100%" height="100%" horizontalAlign="center" verticalAlign="middle">
            <mx:VBox width="100%" height="100%" horizontalAlign="center" verticalAlign="middle">
                <mx:TextInput restrict="0-9" id="depositeInput" />
                <mx:Button label="Deposite">
                    <mx:click>
                        <![CDATA[
 
                            // Directly dispatching of Cairngorm (Use Case) events from within the view
                            var de:DepositeEvent = new DepositeEvent();
                            de.data = Number(depositeInput.text);
                            de.dispatch();
                        ]]>
                    </mx:click>
                </mx:Button>
            </mx:VBox>
            <mx:VBox width="100%" height="100%" horizontalAlign="center" verticalAlign="middle">
                <mx:TextInput restrict="0-9" id="withdrawInput" />
                <mx:Button label="Withdraw">
                    <mx:click>
                        <![CDATA[
 
                            // Directly dispatching of Cairngorm (Use Case) events from within the view
                            var we:WithdrawEvent = new WithdrawEvent();
                            we.data = Number(withdrawInput.text);
                            we.dispatch();
                        ]]>
                    </mx:click>
                </mx:Button>
            </mx:VBox>
        </mx:HBox>
    </mx:VBox>
</mx:Panel>

Okay, to touch on some practices, I have in the above MXML component (and in the rest of the examples) really made an effort in showing some critical points where you should watch how you implement your rendering in the view.
First is the lack of dependency injection; I directly reference the ModelLocator to retrieve the instance of wallet. This is not a favorable approach, as this couples our view unnecessary to the Cairngorm framework (The ModelLocator pattern) - also by not having the dependent Wallet injected at runtime, we miss the opportunity of resetting the component later, by injecting a new Wallet.

Furthermore the DepositeEvent and WithdrawEvent, which is CairngormEvents representing application Use Case Events, are created and dispatched directly from WalletPanel. This approach presents some problems; again the coupling between the view and Cairngorm is unnecessary and our view would not be reusable in another application without modification. Another more underlying thing is that there is no other mediator between views than the actual Domain Model objects. So every change in our view, which might be of interest to other views, would have to be reflected in the Domain Model and the update in the view would have to be related to a Use Case/ CairngormEvent and trigger a around trip in the application(Event -> Controller -> Command -> Model -> View).
To give an overview of what I mean, you should take a look at the below diagram:

A UML diagram of the structure of the application
ex1diagram

Here is the CyberWallet application implemented with the above state of mind. You can view the source by right clicking and choosing “View Source”.  Bear in mind that the application is not created to show case Flex specific things, and *yes* I know a lot could have been done smarter, better, easier,… etc. - these things is not the focus of this post. But what you *should* notice is the way Cairngorm is setup, and how the view retrieves the Model, how it communicates with the controller layer and how the Model is implemented to provide view-centric/specific needs.
I wont go over the other use cases and view parts, as you can see them for your self, in the source. I have commented here and there to point out the violations mentioned above.

The next big step
After working around for some time, getting more and more familiar with Flex and of cause Cairngorm, I started getting rid of some of the “teething troubles”. All the apparent issues from the example above; better dependency injection, decoupling between view and Cairngorm and a better formalization and evolution of the supporting model objects (-managing, -monitoring …). I started looking at these classes or coherent collections of responsibility as representational model objects; representational in the way, that these objects would represent smaller or larger parts of the Domain, without being an actual Domain Model them selves. If I today should categorize them I would say that they where “Passive Presentational Models” or “Ultra Thin Presentational Models”. - if you are not familiar with Martin Fowlers work on “Organizing presentation logic“, you should definitely take at look.

This way of moving away from having view-needed properties in the Domain, and instead having coherent parts of the Domain represented in “Thin Presentation Models” felt better. “A snake in paradise”- as my architecture started looking better when facing the model layer, I had started getting a lot of concerns in terms of how much logic to have in the Views. I had tried code behind for a while, which I really didn’t like due to the strong coupling, I turned to a more “View Helper” centric approach, where a view would have a helping class, handling events, states etc. - they never really felt like the best solution, and even though it wasn’t coupled trough inheritance like Code Behind, but trough composition, I had the same “to tight coupled” feeling as I had with Code Behind.

Another thing I didn’t like was the “one view, one helper” situation. -I really wanted to find a pattern where multiple views could feed from the same “source”. So I actually returned to putting logic back to the view. I started using an “Aggregated  View” pattern (there is no such pattern, but I invented the term internaly when talking about this aproach), where I would declare the new  Cairngorm-decoupled, reusable view components. These view components would depend on a “Thin Presentation Model” and be structured inside a custom wrapping view managing multiple components; hence the name “Aggregated View”. The idea was to have as much reusable code in coherent components, and let the “Aggregated View” handle the dependency injection, and gathering component level events, and interpreted them into use case events / CairngormEvents if necessary. This way the custom code would be minimized to evolve around setting up the reusable components, and only be inside these AggregatedView phenomenoms.

Example 2
From example 1, to 2, there is two very distinct points I would like to iterate over. First I will take a look at how the Domain Model is held clean, and the use of the “Thin Presentation Model” - called Representation i the following, as it is not an actual implementation of the Presentation Model pattern.
Next I’ll look at the “Aggregated View” I mentioned before, to show how our custom have been limited to a central mxml file (view).

The Wallet revisited
Lets start by looking at the revisited Wallet class in the Domain layer. What you should notice is the total removal of [Bindable] tags. As mentioned earlier, I’m not a big fan of these in Domain Models. Instead you’ll see that the Wallet now is an EventDispatcher descendant, and that I have introduced a TransactionEvent, being fired when ever a Transaction has occurred in the Wallet. The state property is gone as is the transactionNotes. These responsibilities would now be found in the Representation of the Wallet. This way of having the Domain Cleaned out, only using pure ActionScript features such as dispatching events etc, is how I still is implementing my Domain Models today.

Wallet Class

?View Code ACTIONSCRIPT
package model.domain
{
	import event.TransactionEvent;
 
	import flash.events.EventDispatcher;
 
	import mx.collections.ArrayCollection;
 
	public class Wallet extends EventDispatcher
	{
		public var balance:Money;
 
		public var transactionLog:ArrayCollection;
 
		public function Wallet()
		{
			balance = new Money(0,"dollars");
			transactionLog = new ArrayCollection();
		}
 
		public function deposit(money:Money,notes:String):void
		{
			balance = balance.add(money);
			var trans:Transaction = new Transaction(Transaction.DEPOSITE,money,notes);
			transactionLog.addItem(trans);
			dispatchTransaction(trans);
		}
 
		public function withdraw(money:Money,notes:String):void
		{
			balance = balance.substract(money);
			var trans:Transaction = new Transaction(Transaction.WITHDRAW,money,notes);
			transactionLog.addItem(trans);
			dispatchTransaction(trans);
		}
 
		private function dispatchTransaction(t:Transaction):void
		{
			var te:TransactionEvent = new TransactionEvent("transaction");
			te.transaction = t;
			dispatchEvent(te);
		}
	}
}

The EconomyRepresentation
So getting rid of the view and Flex specific noise in the Domain, we can now take a look at the new Representation class used to provide the view specific information from the Wallet. Instead of naming my Representation something like WalletRepresentation, which would remind me to much of the one - to -one mapping in the View Helper pattern, I would name it EconomyRepresentation. As stated, my hope was to move towards a set of patterns giving my a central coherent place for multiple views to subscribe to Domain data. - thus, by going for a broader Representation term, and also the responsibility, I would get a class which could serve all views with interest in presenting parts of the aggregated Domain related to economy.

EconomyRepresentation Class

?View Code ACTIONSCRIPT
 
package model.presentation
{
	import event.TransactionEvent;
 
	import model.domain.Wallet;
 
	public class EconomyRepresentation
	{
		[Bindable]
		public var transactionLog:String ="";
 
		[Bindable]
		public var balance:String;
 
		private var _wallet:Wallet;
 
		public function EconomyRepresentation(wallet:Wallet)
		{
			_wallet = wallet;
			init();
		}
 
		private function init():void
		{
			balance = _wallet.balance.toString();
			_wallet.addEventListener("transaction",walletTransactionHandler);
		}
 
		private function walletTransactionHandler(te:TransactionEvent):void
		{
			transactionLog+= te.transaction.toString();
			balance = _wallet.balance.toString();
		}
 
	}
}

As you can see, the EconomyRepresentation has a reference to the Wallet Domain object. It subscribes to the transaction event broadcasted by Wallet. When this broadcast is handled the EconomyRepresentation updates it’s balance, which compared to the balance of Wallet, is just a string, and the transactionLog compared to the transactionLog in Wallet which is of type ArrayCollection, is again just a string representing the log of the Wallet.
As I think this way of cleaning and separating (re)Presentational logic from the Domain is very essential, I’ll show you the AuthenticationRepresentation and the ApplicationRepresentation as well. These two classes is of cause used to represent the authentication related data and state of the application and the state of the actual-application (actual-application; the overall state of the application). The latter being the workflowState declared directly on the ModelLocator in Example 1 (run the example and view the source).

AuthenticationRepresentation Class

?View Code ACTIONSCRIPT
package model.presentation
{
	import flash.events.EventDispatcher;
 
	public class AuthenticationRepresentation extends EventDispatcher
	{
		public static const LOGGING_IN:String ="loggingIn";
		public static const LOGGED_IN:String ="loggedIn";
		public static const LOGGING_OUT:String ="loggingOut";
		public static const LOGGED_OUT:String ="loggedOut";
		public static const LOGGING_IN_FAULT:String ="loggingInFault";
 
		[Bindable]
		public var state:String = LOGGED_OUT;
 
		[Bindable]
		public var userName:String;
 
		public function AuthenticationRepresentation()
		{
 
		}
 
	}
}

The above is simply just the states we had in the AuthenticationManager plus the userName property.

But what is interesting is to be seen in the following example of the ApplicationRepresentation class

ApplicationRepresentation Class

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package model.presentation
{
	import model.ModelLocator;
 
	import mx.events.PropertyChangeEvent;
 
	public class ApplicationRepresentation
	{
		public static const AUTHENTICATION:String = "authentication";
		public static const IDLE:String = "idle";
 
		[Bindable]
		public var state:String = AUTHENTICATION;
 
		private var _authModel:AuthenticationRepresentation;
 
		public function ApplicationRepresentation(authModel:AuthenticationRepresentation)
		{
			_authModel = authModel;
			init();
		}
 
		private function init():void
		{
			_authModel.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,authModelChanged);
		}
 
		private function authModelChanged(e:PropertyChangeEvent):void
		{
			if(e.newValue == "loggedIn")
				state = IDLE;
			else if(e.newValue =="loggedOut")
				state = AUTHENTICATION;
		}
 
	}
}

- the ApplicationRepresentation is dependent on another Representation, to manage it’s states. This is for me a vital feature in the design; being able to structure hierarchical Representations of the Domain. If you look at the source of Example 1 you would see that in the LogInCommand I would manipulate both the state of the AuthenticationManager *and* the workflowState property of the ModelLocator. To me, it is more sound to have an association-structure among these Representation models, as we have it at the Domain level of the model. Instead of having our controller feature of the architecture coupled to how our representation is laid out, the LogInCommand should only worry (and know) Representation and/or Domain objects related to pure concept of “Loggin In” - and not derivations!

The Aggregated View
As we looked at the WalletPanel in example 1, I’ll take my point in the same view part here. - the rest of the view implementations can been seen by running the example linked to below, and selecting to view the source.

WalletPanel View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%">
<mx:Metadata>
    [Event(name="withdraw")]
    [Event(name="deposite")]
</mx:Metadata>
<mx:Script>
    <![CDATA[
 
        //Balance is dependent on injection, instead of self-retrieved the data.
        [Bindable]
        public var balance:String
 
        public var depositAmount:Number;
 
        public var withdrawAmount:Number;
    ]]>
</mx:Script>
    <mx:VBox width="100%" height="100%" horizontalAlign="center" verticalAlign="middle">
        <mx:Label text="Wallet Balance is {balance}" fontSize="30" />
        <mx:HBox width="100%" height="100%" horizontalAlign="center" verticalAlign="middle">
            <mx:VBox width="100%" height="100%" horizontalAlign="center" verticalAlign="middle">
                <mx:TextInput restrict="0-9" id="depositeInput" />
                <mx:Button label="Deposit">
                    <mx:click>
                        <![CDATA[
                            depositAmount = Number(depositeInput.text);
                            dispatchEvent(new Event("deposite"))
                        ]]>
                    </mx:click>
                </mx:Button>
            </mx:VBox>
            <mx:VBox width="100%" height="100%" horizontalAlign="center" verticalAlign="middle">
                <mx:TextInput restrict="0-9" id="withdrawInput" />
                <mx:Button label="Withdraw">
                    <mx:click>
                        <![CDATA[
                            withdrawAmount = Number(withdrawInput.text);
                            dispatchEvent(new Event("withdraw"))
                        ]]>
                    </mx:click>
                </mx:Button>
            </mx:VBox>
        </mx:HBox>
    </mx:VBox>
</mx:Panel>

If we look at the above, we see that the WalletPanel firstly defines some component-context events in the metatags, and that there is no dispatching of CairngormEvents from inhere anymore. This separation keeps the WalletPanel decoupled from the CairngormEvents, and instead we are able to handle the two component-defined events as we want out side of the component (for instance mapping them to CairngomEvents/use cases). Next, the balance property is declared as bindable thus making it an injection hole from the outside world, into the WalletPanel. The WalletPanel do not have to rely on the ModelLocator or any Domain Models to get its data; it simply expects it to be injected. In a matter of convenience I have provided the WalletPanel with two public properties, used by the outside world to retrieve the deposit and withdraw amounts respectively.
To make this new revision of the WalletPanel, and the other new view components work, we need to look at our “AggregatedView”. In Example 1, we already had a view class aggregating and laying out the components; the LayoutView.

The LayoutView has in this example been modified to hold the application-specific code gluing the cleaner panel components and the representations together. The LayoutView works as a Mediator for the containing components, and injects their dependencies when declaring them. Lastly it handles the component-specific and dispatches the equivalent Use Case events (CairngormEvents).

LayoutView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" 
    height="100%" xmlns:login="view.login.*" xmlns:status="view.status.*" 
    xmlns:wallet="view.wallet.*"
    currentState="{_model.applicationModel.state}">
<mx:Script>
    <![CDATA[
        import event.WithdrawEvent;
        import event.DepositEvent;
        import event.LogOutEvent;
        import vo.UserVO;
        import event.LogInEvent;
        import model.ModelLocator;
        [Bindable]
        private var _model:ModelLocator = ModelLocator.getInstance();
 
        private function handleLogIn(event:Event):void
        {
            var user:UserVO = new UserVO();
            user.userName = loginPanel.user;
            user.password = loginPanel.pass;
            var loginEvent:LogInEvent = new LogInEvent();
            loginEvent.data = user;
            loginEvent.dispatch();
        }
        private function handleLogOut(event:Event):void
        {
            new LogOutEvent().dispatch();
        }
        private function handleDeposit(event:Event):void
        {
            var de:DepositEvent = new DepositEvent();
            de.data = walletPanel.depositAmount;
            de.dispatch();
        }
        private function handleWithdraw(event:Event):void
        {
            var we:WithdrawEvent = new WithdrawEvent();
            we.data = walletPanel.withdrawAmount;
            we.dispatch();
        }
    ]]>
</mx:Script>
    <mx:VBox id="leftPanel" horizontalAlign="center" horizontalCenter="0">
        <status:StatusPanel id="statusPanel" statusLog="{_model.ecoModel.transactionLog}" />
        <login:LogInPanel id="loginPanel" authenticationModel="{_model.authenticationModel}" 
            logIn="handleLogIn(event)"
            logOut="handleLogOut(event)" />
    </mx:VBox>
    <wallet:WalletPanel id="walletPanel" balance="{_model.ecoModel.balance}" 
        deposite="handleDeposit(event)" withdraw="handleWithdraw(event)">
    </wallet:WalletPanel>
 
    <mx:states>
        <mx:State name="authentication">
            <mx:SetProperty target="{statusPanel}" name="width" value="0" />
            <mx:SetProperty target="{statusPanel}" name="height" value="0" />
            <mx:SetProperty target="{walletPanel}" name="width" value="0" />
            <mx:SetProperty target="{walletPanel}" name="height" value="0" />
            <mx:SetProperty target="{leftPanel}" name="width" value="100%" />
            <mx:SetProperty target="{leftPanel}" name="height" value="100%" />
            <mx:SetProperty target="{loginPanel}" name="width" value="25%" />
            <mx:SetProperty target="{loginPanel}" name="height" value="25%" />
        </mx:State>
        <mx:State name="idle">
            <mx:SetProperty target="{statusPanel}" name="width" value="100%" />
            <mx:SetProperty target="{statusPanel}" name="height" value="100%" />
            <mx:SetProperty target="{walletPanel}" name="width" value="100%" />
            <mx:SetProperty target="{walletPanel}" name="height" value="100%" />
            <mx:SetProperty target="{leftPanel}" name="width" value="25%" />
            <mx:SetProperty target="{leftPanel}" name="height" value="100%" />
        </mx:State>
    </mx:states>
 
    <mx:transitions>
        <mx:Transition fromState="*" toState="*">
            <mx:Resize targets="{[walletPanel,leftPanel,statusPanel,loginPanel]}" />
        </mx:Transition>
 
    </mx:transitions>
</mx:HBox>

Here is an overview of the application in UML. I have in the diagram also showed the Business package, which is not implemented in the example. This is only to show how it would have been implemented until now. A Command would instantiate a Delegate, which would use a service found in the ServiceLocator. The Delegate would be the first to handle the result back from the Service, massaging it in any way needed for the application. This could for instance be an XML stream needed to be parsed to the equivalent object hierarchy. After this, if any, data massaging the data would be send back to the Command, as a response. This way the only place where a Service and raw data manipulation would be at place, would be in the Delegate classes.

You can see the example and view the source here.
Key points in this example was the cleaning of the Domain layer and the more powerful Representation models. The view also got cleaner and an “AggregatedView” gluing it all together, providing injection and providing mediation between Views was created.

The real Presentation Model
At this time in my Cairngorm journey I started feeling good again about my work as an Architect , or as Ben Stucki might have put it; I started to move my Developer <-> Architect slider thumb, more towards the Architect end again (see Bens’s great talk about Developers and Architects). The way the cleaning of the Domain layer had turned out, was satisfying and reusable components decoupled from their surroundings also felt good. My own interpretation of a Presentation Model aka Representations also did the job good, and abstracted the Domain towards the view. But I still felt I had two challenges: My AggregatedViews also got the job done but their internals was not a pretty sight; custom code gluing it all together, seamed to call for a bloated script tag inside the MXML file. - further I still wasn’t to happy about the View. As mentioned I went back towards the Autonomous View pattern. -where a view manages it’s own presentation logic and state (or greater parts of it, as in example 2).

So the next step on my journey was to take a deeper look at the *real* Presentation Model, which fitted good into my current setup:

  1. My currently implemented Representation layer could easyli evolve into a real Presentation Model
  2. My AggregatedView could be cleaned and the Presentation Model could encapsulate the component/use case specific code, and mediate for multiple views
  3. My semi- Autonomous View could have it’s logic extracted and be a “dumb view” not knowing to much.

So by evolving my Representation into a Presentation I could get closer to a solution to the two above mentioned challenges.

Example 3
To illustarate how to move the logic from  the View into a Presentation Model, I’ll return show how the Authentication Representation class ended up, as Authentication Model:

AuthenticationModel Class

?View Code ACTIONSCRIPT
package model.presentation
{
	import event.LogInEvent;
	import event.LogOutEvent;
 
	import flash.events.EventDispatcher;
 
	import vo.UserVO;
 
	public class AuthenticationModel extends EventDispatcher
	{
		public static const LOGGING_IN:String ="loggingIn";
		public static const LOGGED_IN:String ="loggedIn";
		public static const LOGGING_OUT:String ="loggingOut";
		public static const LOGGED_OUT:String ="loggedOut";
		public static const LOGGING_IN_FAULT:String ="loggingInFault";
 
		[Bindable]
		public var canLogIn:Boolean = false;
 
		[Bindable]
		public var status:String = "";
 
		private var _userName:String;
		private var _userNameIsValid:Boolean = false;
		private var _password:String;
		private var _passwordIsValid:Boolean = false;
 
		private var _currentState:String ="";
 
		public function AuthenticationModel()
		{
			state = LOGGED_OUT;
		}
 
		/**
		 * API method to logIn
		 *
		 */
		public function logIn():void
		{
			if(canLogIn)
			{
				var user:UserVO = new UserVO();
				user.password = _password;
				user.userName = _userName;
 
				var le:LogInEvent = new LogInEvent();
				le.data = user;
				le.dispatch();
			}
		}
 
		public function logOut():void
		{
			//TODO implement logic to secure that log out can take place
			userName = "";
			password = "";
			new LogOutEvent().dispatch();
		}
 
		[Bindable]
		public function set state(value:String):void
		{
			//TODO implement logic for setting state
			_currentState = value;
			if(value == LOGGING_IN)
				status = "Logging In";
			else if(value == LOGGED_IN)
				status = "Logged In";
			else if(value == LOGGING_OUT)
				status = "Logging Out";
			else if(value == LOGGED_OUT)
				status = "Logged Out";
 
		}
		public function get state():String
		{
			return _currentState;
		}
 
		public function get userName():String
		{
			return _userName;
		}
 
		[Bindable]
		public function set userName(value:String):void
		{
			_userName = value;
			validateUserName();
		}
 
		public function get password():String
		{
			return _password;
		}
 
		[Bindable]
		public function set password(value:String):void
		{
			_password = value;
			validatePassword();
		}
 
		/**
		 * INTERNAL validation methods
		 *
		 */
		private function validateUserName():void
		{
			//TODO implement validation logic and rules
			_userNameIsValid = _userName.length &gt; 3;
			validateLogIn();
		}
		private function validatePassword():void
		{
			//TODO implement validation login and rules
			_passwordIsValid = _password.length &gt; 3;
			validateLogIn();
		}
		private function validateLogIn():void
		{
			canLogIn = _passwordIsValid &amp;&amp; _userNameIsValid;
		}
	}
}

Looking at the above we can see that our Authentication Model actually has an API (logIn and logOut) that reflects the use cases we want our Presentation Model to present. These two methods are now handled accordingly to the state of the Model. If, in this case, the password and user name checks out, a CairngormEvent is dispatched to be taken care of by the controller -> command structure. Besides this API you should see that the validation, and enabling of the ability to login is handled by the Model. This is handles by letting the View set the user name and password data each time it changes in the view. - thus making the actual view very thin in terms of knowledge.

I’m still presenting the state of the model as a string (though arguable the state is now composed by multiple data variables) but in other and more complex Presentation Models could easily have a real state (machine) pattern implementation.

Now that we have glimpsed at the new Presentation Model implementation, lets see how the actual View has been changed to correspond to this. In this example I have chosen the LogInPanel as it uses the AuthenticationModel just described. You should check out the running example 3 to see the full source.

LogInPanel View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" horizontalAlign="center" verticalAlign="middle" 
    layout="vertical" width="100%" height="100%"
    currentState="{authPM.state}" title="{authPM.status}">
<mx:Script>
    <![CDATA[
        import model.presentation.AuthenticationModel;
 
        [Bindable]
        public var authPM:AuthenticationModel;
 
    ]]>
</mx:Script>
    <mx:states>
        <mx:State name="loggedOut">
            <mx:AddChild>
                <mx:VBox>
                 <mx:Form>
                    <mx:FormItem label="User Name:">   
                        <mx:TextInput id="userName" change="authPM.userName = userName.text" text="{authPM.userName}" />
                    </mx:FormItem>
                    <mx:FormItem label="Password">
                        <mx:TextInput id="password" displayAsPassword="true" change="authPM.password = password.text" text="{authPM.password}" />
                    </mx:FormItem>
                </mx:Form>
                </mx:VBox>
            </mx:AddChild>
            <mx:AddChild>
                <mx:ControlBar>
                    <mx:Button label="Log In" enabled="{authPM.canLogIn}" click="authPM.logIn()" />
                </mx:ControlBar>
            </mx:AddChild>
        </mx:State>
        <mx:State name="loggingIn">
            <mx:AddChild>
                <mx:Label text="Logging in, please wait..." fontSize="22" />
            </mx:AddChild>
        </mx:State>
        <mx:State name="loggedIn">
            <mx:AddChild>
                <mx:VBox>
                    <mx:Label text="You are logged in as {authPM.userName}" />
                </mx:VBox>
            </mx:AddChild>
            <mx:AddChild>
                <mx:ControlBar>
                    <mx:Button label="Log Out" click="authPM.logOut()" />
                </mx:ControlBar>
            </mx:AddChild>
        </mx:State>
        <mx:State name="loggingOut">
            <mx:AddChild>
                <mx:Label text="Logging out, please wait..." fontSize="22" />
            </mx:AddChild>
        </mx:State>
    </mx:states>
</mx:Panel>

As you can see; the View is much cleaner and stripped from most of the logic. After the reference to the AuthenticationModel gets injected the LogInPanel binds all its View Controls to state and properties of the Model. The change event fired from the TextInput fields is defined to set the property on the Model, and the buttons’ click events are triggering parts of the Models interface.

With the thinning of the View our AggregatedView also get stripped down, actually to only having the responsibility to inject the dependencies (and in our case to lay out the components).

You should take a look at the source in example 3.

Another thing worth noticing is that in my example, I have two kinds of Presentation Models; one being strictly the Abstracted View logic, and nothing more (Authentication Model) and the other being both an Abstracted View Model but also a Domain related Representation (Economy Model as it is directly associated and defined by states in Domain Model objects). I call them the Self Sustained Presentation Model, and the Domain Sustained Presentation Model respectively. We could even go further and claim that the Application Model in the Examples is a Specialized Presentation Model as it’s presentation ground remains on other Presentation Models, hence the specialization.

Overview
So lets look at how the structure is.
In the below UML diagram I have sketched out how the concepts and patterns collaborate in example 3. I have not named the classes as they are named in the Cyber Wallet domain, but as their role in the architecture. As in the diagram in example 2, I have included the Business package of the architecture, thugh it is not implemented and discussed in the examples. In this state of the architecture a new “player” has entered the field; the responder. In stead of having the delegate class or the command handling the respons from the business layer, an actual responder is given the responsability of handling and massaging data. So in terms of the GRASP (-patterns) the coherent responsability of handling business respons, is moved from either the command which only executes in regard to its concrete implementation and the delegate only delegates the business tier, to a responder (in the diagram the Responder and the Delegate should also be understood as conceret implementations of the concepts respectively).

Retrospective
The Presentation Model provided a good solution to my two mentioned challenges. The View got thinner, the AggragatedView got cleaned out, and the Representation Models matured and grew up and took on more coherent responsibility. And this is about where I am to day. I’m constantly looking at new patterns, thinking about ways to improve here and there. There is still many places where I haven’t been with Cairngorm yet, so I’m sure that my journey is far from over. - I hope it’s not. I hope there is many miles left to be wandered, as sometimes it’s not what you ended up with that matters the most, but the way you got it! It has been great walking down memory lane, and fun to see how perspective and understanding changes over time.

Looking a head I’m very excited to see Gumbo/Flex 4 evolve. As I write this, a lot of the specs for the new Flex 4 component architecture has been released. This new separation between presentation logic and graphical View is very interesting and I am really looking forward to how this will be adding to the next step in my Cairngom journey, and the way I work together with the great designers in our team.

My first Cairngorm Confession is over; a journey looking back at the last couple of years in my life with Cairngorm and Flex. It has been great to stroll down memory lane. I hope you enjoyed it as well.
Again, I want to re-iterate that the examples isn’t created to show best practices in Flex, maybe not even in Cairngorm, but to support my rambling about the concepts I got to know and evolve around in my journey. I might find the time to clean the examples up, and make them nice and consistent, but for now I hope you’ll live without and are able to derive the essence of my points.

ActionScript, Cairngorm, Cairngorm Confessions , , , , , ,

Comments are closed.