Lighter & Faster workflow for robotlegs: might be better?

webdesignporto's Avatar

webdesignporto

24 Nov, 2010 01:34 AM

Hello,

Currently I'm building quite a large app with flex4+robotlegs+as3signals+google app engine based on python.
1) Well the problem is that I'm finding myself redipsatching the same signals from the view to mediator and from mediator to context.
2) The other problem is that I'm executing commands for signals from mediators that just run 1 method on the model.

Remember that working with flex 4, the skin might be separed from the business logic.
So I have a skin, a component for that skin and a model. That is my new approach: the component listen on skin. The component has strong typed model objects (injected) and call methods directly. The component listen for model signals to dispatch. That's all for the components. The mediators are converted in the component. And the command is not used.
I attached some files, they contain workflow examples and theory. old and new!

Now the problem is:
Instead of mapping views (in my case I call them components) to mediators, just map the component with no mediator attached, as is not used. Then allow the component to use the [Inject] metatag.

Just this is what I need, any idea on how to implement it?
And of course, I would be glad to hear your oppinion and critcize on it, it always help to improve ;)

thanks for your time! ;)

  1. 1 Posted by mike.cann on 24 Nov, 2010 08:19 AM

    mike.cann's Avatar

    I am likely to be shot down by the SoC gods for this, however I really like this method you have outlined.

    I in fact am using this exact method in several of my personal projects at the moment. I use the viewMap in the context to map my views directly. I then have models directly injected into the views. This way I can then bind to properties on my model / proxy without being forced to wrap a single proxy variable in a VO simply so I can bind to it.

    I do use commands still for some of the larger calls and calls with no particular owner. I try to group all functionality that directly involves that Model/Proxy inside of there and if there are any methods that dont strictly belong to that proxy or to any other proxy ill make a command for it. It just seems to make sense for me. I also like to use commands for async processes that could be fired multiple times such as calls to the server.

    For me personally, this method works. It brings a nice level of separation (in my head anyways) but at the same time isnt so verbose that it makes me feel like the framework is bogging me down forcing me to jump through hoops to get a simple thing done.

    I have to thank the RobotLegs team for doing such a great job when constructing the framework, allowing this level of flexibility to modify it as you choose!

  2. 2 Posted by Stray on 24 Nov, 2010 10:04 AM

    Stray's Avatar

    Injecting into views?? Yikes! Now your views are coupled to your application logic. Really views shouldn't care what the model API is, they should only care how to do their view stuff.

    Where I think we differ is that us SoC disciples (who will call on our gods to unleash a plague of frogs on you later) believe it's faster and easier to work with code that does just one thing and is decoupled from other classes as far as possible.

    When, later, you change that model API, you'd have to change ALL the views it's injected into. Or you're fighting with legacy code because you can't change it because the change would be too labour intensive.

    And it's really not very easy to do TDD that way ... but if you're not on that bus either then I guess that doesn't matter.

    But... if it works then it works. And if there's one thing robotlegs isn't, it's prescriptive. Proceed with caution though... and keep some painkillers handy.

  3. 3 Posted by mike.cann on 24 Nov, 2010 10:43 AM

    mike.cann's Avatar

    Haha I didnt think the gods would look too kindly on this :P

    I think the method that webdesignporto is talking about means that the skin has no knowledge about the model / proxy itself and so there is a sepparation there between the presentation part of the view and model itself. In his method (and mine) the view components in effect become the mediators taking on the responsibly of view to application communication in addition to providing the data for the presentation to function.

    As for TDD, well you know me Stray, I havent yet worried about that world (I intend to soon tho I promise!) and I suspect you would be correct it may make things harder to test.

    For me the last year or so has been a journey through design patterns trying to find the one that is the best fit for the way I like to work. This is simply the latest iteration, should I suddenly, or more likely, over a period of time find a situation where this method breaks down ill look for a better way of doing things.

    I guess im just a big-headed programmer who needs to make the mistake himself to see the right way :P

  4. 4 Posted by Stray on 24 Nov, 2010 10:57 AM

    Stray's Avatar

    > the view components in effect become the mediators taking on the responsibly of view to application communication in addition to providing the data for the presentation to function.

    Your poor views! So many responsibilities! The should be contacting their union and demanding a pay rise.

    It's fine to have to bash your own head :)

    What you're really doing is using less framework. It's DI + signals + signalCommandMap.

    I can see that in games, cutting out the middle man might be useful in terms of speed increase for the user. But... can you at least use an injected Signal or SignalBus and stop injecting those models into the views? If you use a request-and-response signal then you're only bound to the signal's signature and not the model itself.

    Oh.. I just had an idea for a blog post. Nice one!

  5. 5 Posted by webdesignporto on 24 Nov, 2010 11:20 AM

    webdesignporto's Avatar

    @Stray: The problem I'm having is this:

    My component dispatch the loginSignal. Mediator catches and redispatches the same loginSignal, but this one is the injected one. So in this is already I repetition that I don't really need!
    Then when the injected loginSignal is dispatched the command logingCommand catches it, in it is inserted the loginVO, that contain the email, password and rememberMe. Then the loginCommand call the login method on the authorization model class. Then the authorization model dispatch the event loginSuccess. The loginMediator catches it and edit the component.

    This is really really sick! xD

    The way I'm thinking to do is:
    The skin listen for user interraction. Then dispatch events to the component. The component make some business login and then access the authorization.login(loginVO). Then when the model updates notify the user...

    That is a lot faster and easier to change!

    Imagine in the first case I want to change the login method to loginMeNow.
    1) change signal variables name in the view, mediator, context and signal class name;
    2) The name of the method in the model;
    3) The loginVO to loginMeNOwVO;
    4) Command name to loginMeNowCommand;
    5) The method I call on model from command;
    5) change the signal that model dispatches;
    maybe more..

    In my approach you have to change:
    1) The method name to loginMeNow on the authorization;
    2) change the value object like in the old way;
    3) change the method I call on the view;
    4) change the signal that model dispatches;

    I think you don't want to compare the time and the attention you need in order to change just this thing. Then to add another method on the model and to call it you would need to create a new signal, command, map the signal to command and a lot of sick things! no dry, just a lot of unusefull repetition...

  6. 6 Posted by webdesignporto on 24 Nov, 2010 11:22 AM

    webdesignporto's Avatar

    @Mike Cann: Yes that's right the skin is just mxml and do not have any business logic. Read my last comment..

  7. 7 Posted by mike.cann on 24 Nov, 2010 11:25 AM

    mike.cann's Avatar

    Ha! My views like the added responsibly it allows them to have all the data they need to make the view look pretty and update automatically thanks to [bindable], they are also able to have a word with the model if they need to make a quick change.

    Well I am using less of the framework I suppose however the framework is essentail for mapping the views, commands and proxys and performing the automatic injection magic that glues it all together.

    Cutting out the middleman just gets me there a little faster. SignalBus eh? Yes I would certainty like to hear more about this!

  8. 8 Posted by webdesignporto on 24 Nov, 2010 11:36 AM

    webdesignporto's Avatar

    Can you explain how is the workflow is in your application, or just an example of it?

  9. 9 Posted by mike.cann on 24 Nov, 2010 11:39 AM

    mike.cann's Avatar

    @webdesignporto I think this is where you and I differ slightly.

    With respect to your login example I may be tempted to use a command or service in this instance.

    What I think you are suggesting is to remove the "eventBus" type architecture altogether and just have whatever needs to listen for the "loginSuccess" signal on the model. The problem I see with this is that now anything else that needs to listen for a "loginSuccess" now has to have a reference to the "LoginModel" or whatever its called.

    What I personally would do was have the "LoginModel" or perhaps "LoginService" dispatch an appication level event/signal to the eventBus which can then be picked up by the view/mediator or whatever else needs to know about this.

    I agree with you however that verboseness is 'sick' however sometimes you have to make the trade between spaghetti and verboseness.

  10. 10 Posted by Stray on 24 Nov, 2010 12:01 PM

    Stray's Avatar

    I create the VO in the mediator.

    So the view dispatches a login(username, password) signal.

    The mediator creates the loginVO(username, password)

    I use events still for my in-application communication in some instances, signals in others. So - the mediator dispatches the LoginEvent.LOGIN_REQUESTED and a command picks that up, passes it to the service and away we go.

    I don't think that's gross... if I needed to change my VO I'd have to change it only in 2 places - the mediator and the service. (And my tests, but that's why I use support class wrappers around VOs etc used by the test.)

    If an object is created someplace and used someplace else, I don't see how you can get away from that?

    I also think that verboseness is 'sick' - but I think I'm using it in a different sense of the word!

    The point of separation of application logic and view logic is that your view and application can then be varied separately. If you don't mind that your layers are coupled together in ways that mean that you can't change the application without also changing the view then that's not going to be important to you.

    It really all depends on the scale and purpose of your app... the size of the team etc.

  11. 11 Posted by webdesignporto on 24 Nov, 2010 12:08 PM

    webdesignporto's Avatar

    The mediator in order to listen for the signal dispached by the model must have a reference to the injected signal; so no much diference anyway... or I'm missing something?

  12. 12 Posted by Stray on 24 Nov, 2010 12:33 PM

    Stray's Avatar

    It's not listening to the model - it's listening to the view.

    But also - yes, there is a *huge* difference between injecting a signal into your mediator (which I do do) and injecting into your view.

    Injecting the model in the view is horrible coupling - the view is coupled to the model.

    Injecting a signal that is wired to the model into the view is a much cleaner approach - now the view is only coupled to a signal, and as long as something is on the other end of the signal it doesn't care what that is.

    But - the view still relies on an external player to be viable.

    With the mediator in place, the view can be totally self contained. It can be developed and tested in isolation. Any changes affecting the application can be dealt with in the mediator.

    Lots of small classes that all have a clear responsibility - it's more testable and each class can be developed and then closed. The focus of each class is smaller, and so it follows that the focus of the developer is smaller while writing that code. It's an approach that helps to dilute the complexity of app development by breaking it into chunks which have very low complexity.

    But.. if what you're really talking about is the presentation model pattern, then I think it's accepted by those who use it to use a 2 layer system: mxml/PM

    The big gain is injecting the signal not the model.

  13. 13 Posted by webdesignporto on 24 Nov, 2010 12:51 PM

    webdesignporto's Avatar

    But my view is the skin. The component is like a controller in the mvc.

    What is tight to what in my app:

    My way: the component is tight to a model and a skin, but I can still change the models and skins.

    Your way: the mediator is tight to the view;

    Then, my component is tight to a model;
    Your mediator is tight to a signal, if you use signals of course. I don't even remember how it was using events. Your's is less tight, but you must put a lot of signals for each method on the model..

    So if my model method name changes your signal must change too.
    If I add a new mehod on the model, you must add a new signal.

    And yes, the only thing I think is better in your approach is that you can test parts quite easier...

  14. 14 Posted by Stray on 24 Nov, 2010 01:06 PM

    Stray's Avatar

    My views/mediators have no coupling at all to the model API. If a signal is injected then it's created elsewhere and injected into both the mediator and the model (not always through [Inject] - sometimes just a setter). My mediator is coupled to the signal, and the view of course. That's kind of the point of mediators - there's no way to have zero coupling, so isolating it in known places is good.

    I don't have a lot of signals on the models - they mostly would just have 'updated' and pass a VO of current state.

    It's not really about better / worse (and I really was only responding to your question which asked whether your approach was sound - perhaps you only wanted confirmations and not challenges?) - it's about what's appropriate for the journey you're on. If you're building an app in a few days or a week then you can get away with all sorts of things that would be traps on a year-long project. If you have multiple developers you need a different approach to if it's just yourself.

    Ok - so your view is the skin, and your PM is what you're calling 'view' previously. PM (as I understand it - I never use it) uses signals dispatched by the PM, and updates through bindings.

    Why does more classes == bad? A lot of Uncle Bob's refactorings result in more code, not less code, but the code is easier to change and follow.

    It all depends what you think your bottle neck is. For me, typing and making classes is never, ever my bottle neck. It is always *thinking* that is the hard part. And really following the Single Responsibility Principle massively reduces the amount of thinking I have to do in each class - and I spend most of my time writing the app, not architecting it.

    Perhaps you are just quicker at thinking than me!

  15. 15 Posted by mike.cann on 24 Nov, 2010 01:48 PM

    mike.cann's Avatar

    Oh Stray is so good! She almost has me converted from my wicked ways but not quite yet.

    I dont think any sane coder would disagree with the theory behind the Single Responsibility Principle however getting to that nirvana may unacceptable trade offs be it in production or performance time.

    I suppose time will tell if my 'shortcuts' will end up biting me in the behind :P

  16. 16 Posted by webdesignporto on 24 Nov, 2010 04:11 PM

    webdesignporto's Avatar

    In my case I have this:

    ListsModel
    -getListsForUser(userKey) -getListsForCurrentUser() -deleteList(listKey) -addNewList(listVO) -deleteList(listKey)

    Then I must make a signal and command for each method. To change it, is really painfull and to wrap my mind like this is too much for me.
    skin->component->mediator->command->model->service->server->service->parser->service->model->mediator->component->skin

    thinking that all -*> are based on signals/events... it's ust too hard. I've already done it, and not really usefull for me. It is decoupling everything.

    I think is possible to do, but is too rigid, what i mean with this is that to make changes you have to change a lot of code...

    I would like to know what other downsides might have my approach in large apps?

  17. 17 Posted by webdesignporto on 24 Nov, 2010 11:53 PM

    webdesignporto's Avatar

    I just built my simple framework..
    I will test on it and I will make you know how it woks. Inspired from robotlegs, a lot! xD

    package ***{

    import flash.display.DisplayObjectContainer;
    import flash.events.Event;
    
    import org.swiftsuspenders.Injector;
    import org.swiftsuspenders.Reflector;
    
    public class Context{
    
        protected var _targets:Array = [];
        protected var _injector:Injector;
        protected var _reflector:Reflector;
        protected var _injectionsMap:InjectionsMap;
    
        public function Context(){
            _injector = new Injector();
            _reflector = new Reflector();
            _injectionsMap = new InjectionsMap();
    
            _injector.mapValue(Injector, _injector);
            _injector.mapValue(Reflector, _reflector);
            _injector.mapValue(InjectionsMap, _injectionsMap);
    
            _injector.injectInto(_injectionsMap);
    
    
            startup();
        }
    
        public function startup():void{
            // override in your context
        }
    
        public function mapComponent(component:*):void{
            _injectionsMap.add(component);
        }
    
        public function mapModel(model:*):void{
            mapSingleton(model);
        }
    
        public function mapSingleton(whenAskedFor : Class, named : String = ""):void{
            _injector.mapSingleton(whenAskedFor, named);
        }
    
        public function mapSingletonOf(whenAskedFor:Class, useSingletonOf:Class, named:String = ""):*{
            return _injector.mapSingletonOf(whenAskedFor, useSingletonOf, named);
        }
    
        public function set addTarget(value:DisplayObjectContainer):void{
            _targets.push(value);
            value.addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler, true, 0, true);
        }
    
        protected function addedToStageHandler(event:Event):void{
            _injectionsMap.injectInto(event.target);
        }
    
    }
    

    }

    AND

    package ***{

    import org.swiftsuspenders.Injector;
    import org.swiftsuspenders.Reflector;
    
    public class InjectionsMap{
    
        [Inject]
        public var injector:Injector;
    
        [Inject]
        public var reflector:Reflector;
    
        protected var _mappedComponents:Array = [];
    
        public function InjectionsMap(){}
    
        public function add(component:Class):void{
            var i:int = 0;
            for(; i < _mappedComponents.length; i++){
                if(reflector.getFQCN(component) == _mappedComponents[i]){
                    return;
                }
            }
    
            trace('Will inject into, when added: ' + reflector.getFQCN(component));
            _mappedComponents.push(reflector.getFQCN(component));
        }
    
        public function injectInto(component:*):void{
            var i:int = 0;
            for(; i < _mappedComponents.length; i++){
                if(reflector.getFQCN(component) == _mappedComponents[i]){
                    trace('Injecting into: ' + reflector.getFQCN(component));
                    injector.injectInto(component);
                }
            }
        }
    }
    

    }

    ----- end of the framework ---- MainContext.as
    override public function startup():void{

            // components
            mapComponent(LoginComponent);
            mapComponent(WorldComponent);
    
            // models
            mapModel(AuthorizationModel);
            mapModel(AccountModel);
            mapModel(WindowModel);
    
            // signals
        }
    

    fast and easy! (:

  18. Stray closed this discussion on 11 Feb, 2011 10:32 PM.

Comments are currently closed for this discussion. You can start a new one.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac