Manually mapped injection vs depency request

creynders's Avatar

creynders

31 Mar, 2011 02:10 PM

Hi all,

Today I ported RL + SS to a very lightweight version in JavaScript. Maybe 'port' isn't the correct word, it's more of a derivative, since JS is substantially different I took the liberty to change a lot.
I'm using manual injection point mapping since you can't annotate as you can in AS3 and while doing this, I started wondering about something: what's the benefit of manually mapping the injection points for framework actors vs letting framework actors request their dependencies from the injector? (To be clear: I'm not asking what's the benefit of an injection framework, since it has it's obvious, huge advantages to be able to inject dependencies into classes that are not tied to a specific framework)
It seems to me that it's more beneficial to let framework-aware objects request their dependencies, since you don't need the extra step of having to add the injection point mapping manually? Or am I missing something vital here?

  1. 2 Posted by Stray on 31 Mar, 2011 02:28 PM

    Stray's Avatar

    2 things spring to my mind:

    1. If the class has to 'request' then it becomes context aware. The ability to know how to get its dependencies is generally outside of its SRP, and then you can't use the class outside of the presence of that context.

    2. Ability to test - your test would be obfuscated because instead of providing a different version of a dependency (or just a mock), you'd have to provide those to the context that the framework uses to get its dependencies. To me that feels like it would be confusing. Of course it could be done, but you'd have more headaches.

    If you're talking about mediators and commands - 1 is probably not a problem. But for models and services, I think both are a potential source of irritation.

    Would it even really count as DI? I guess it's the Service Locator version... and my understanding is that Service Locator and DI are opposing forces.

    There's a section on SL vs DI by Mr Fowler here: http://martinfowler.com/articles/injection.html

    But... my warning would be that they are not the same, and robotlegs was designed for autoDI, so if you attempt to convert it to SL container/pattern instead your mileage may vary a great deal.

    It's possible that it's a case of "If you're going there, I wouldn't start from here".

  2. Support Staff 3 Posted by creynders on 31 Mar, 2011 05:21 PM

    creynders's Avatar

    Thanks Stray for the eloquent answer, as always.
    Re:

    1. How is this different from the meta-data annotation? I mean: doesn't that make the class also context aware?
      And yes, I too think it wouldn't suit models and services.

    2. yes, that's a very valid point. But wouldn't it be nearly as easy to provide a testing context with mock objects for dependencies as passing them directly to the to-be-tested class?

    And, just to be clear, it wasn't my intention to convert it to SL. I was just wondering whether in commands it wouldn't be easier to request depencies through the injector, instead of mapping the injection points of all commands?

  3. 4 Posted by Stray on 31 Mar, 2011 05:56 PM

    Stray's Avatar

    > Re: 1. How is this different from the meta-data annotation? I mean: doesn't that make the class also context aware?

    The injection meta-data is supplementary. So - whether you use constructor, setter or property injection, you can (as in your tests) instantiate the class totally normally.

    But that *is* the reason why we've resisted the temptation to use anything other than minimal meta-data.

    >
    > 2. yes, that's a very valid point. But wouldn't it be nearly as easy to provide a testing context with mock objects for dependencies as passing them directly to the to-be-tested class?

    Yes - it's only a small extra load. SL and DI are both quite testable provided you type to an IFace for the SL and you can easily replace the normal object with the test object. It only becomes horrible when you start using Static getInstance() and so on.

    My preference would still be to use an alternative - given that you're going to be using some sort of factory to generate your commands anyway. I'd be more inclined to add an extra function that returns a list of setters and class types required, and still have the injector fulfil them. In AS3 it would be something like:

    public function set someDependency(value:ISomeDependency):void
            {
    // etc
    }

    public function get dependencyList():Vector.<IDependencyConfig>
    {
    var dependencyVector:Vector.<IDependencyConfig> = new Vector.<IDependencyConfig>();
    dependencyVector.push ( new DependencyConfig( this.someDependency, ISomeDependency));
    // and more
    return dependencyVector;
    }

    I can see the argument that this code is 'extra' - but so is the SL code. And at least because this code is truly supplementary - it only handles wiring - you're free to ignore it and use your code without using this list and provide the dependencies some other way.

    But personally I just prefer pretty much anything to the SL pattern, other than where the SL is for assets/stylesheets and so on.

    Either is probably fine,

    Stray

  4. 5 Posted by Eric Priou on 31 Mar, 2011 06:40 PM

    Eric Priou's Avatar

    Hi,
    I'd be interested to dig in your js port, if(v opensource, obviously…

    Thanks,

  5. Support Staff 6 Posted by creynders on 01 Apr, 2011 07:14 AM

    creynders's Avatar

    @Stray

    The injection meta-data is supplementary. So - whether you use constructor, setter or property injection, you can (as in your tests) instantiate the class totally normally.

    Yes, that's an excellent point.

    I'd be more inclined to add an extra function that returns a list of setters and class types required, and still have the injector fulfil them.

    And that's an excellent solution, I think. It reminds me of the notificationInterests in PureMVC, if I remember correctly. I think I'll implement that (additionally) into the JS port.

    @Eric: I'm definitely planning on releasing the source and as soon as possible, but for the moment it's not ready for that step yet. I'm using it in a project which is consuming all my time, but I hope to drop it on GitHub in May. Also, at the moment I'm still in doubt on what is essential and beneficial to port to JS and what should be modified to a more JS-mind of programming. The lack of interfaces alone is a challenge.

  6. Support Staff 7 Posted by Stray on 02 Apr, 2011 11:33 AM

    Stray's Avatar

    Nice one! Sounds interesting - and I applaud your willingness to work in JS :) I still have too many scars from debugging as1 to contemplate going back there...!

  7. Support Staff 8 Posted by creynders on 03 Apr, 2011 10:42 AM

    creynders's Avatar

    I hear you :)
    It has been a very pragmatic choice to use JS, I'm working with phonegap at the moment, since we'll need to target our app to various mobile devices and I thought/hope it will be easier and faster to port it to other platforms using Phonegap with JS instead of going the platform native road. I'll know whether that was a good choice in a few months.
    Anyway, after one day of swearing, sweating and throwing things at my brand new computer I realized I needed a framework that feels like home :)

    Is it ok if I ask you a few architectural questions once in a while? There are many hard decisions to be made.

    For instance: the view with mediator pattern seems somewhat out of place in a JS context. You could compare HTML + JS with mxml + code behind, but since there's no equivalent to a display list and most of the times the HTML is already present (ie not dynamically loaded), I can't make up my mind on whether to:

    1. manually activate view + mediator

    2. merge view with mediator into a viewdiator

    Somehow a view + mediator also feels like overkill in JS, but on the other hand having viewdiators will tie the presentation logic to the framework which is something I would like to avoid as much as possible.

    Any genius ideas on alternatives or reasons why I should choose one over the other?

  8. 9 Posted by Jeremy Ruppel on 21 Apr, 2011 07:04 AM

    Jeremy Ruppel's Avatar

    @creynders, you're absolutely right, the view/mediator pattern is totally out of place in a JS context. To pour salt in the wound, there is no consistent support of the mutation events introduced in the DOM level 2 spec across browsers. This would be analogous to our ADDED_TO_STAGE event, but in JS we're SOL.

    I've been working on a similar 'port' of robotlegs to JS for a couple months now. If this conversation isn't stale, take a look at the legs-js github repo and let me know what you think and how it differs from your approach.

  9. Support Staff 10 Posted by creynders on 21 Apr, 2011 07:28 AM

    creynders's Avatar

    Hey Jeremy,
    looks very interesting and you certainly have a different approach at a first glance. For instance if I'm correct all your mappings are named, right? I took a more faithful to the core approach there and used a custom Dictionary to have object to object mapping.
    I was planning on putting up a working version on GitHub, but I'll push an unfinished version soon, so you can have a look at my take on this.
    About the views and mediators, I'm leaning towards having an actorCommandMap and a viewCommandMap (actually just 2 getters to the same commandmap) and skip mediators all together.

    If I'm correct properties get auto-injected if they have the same name as a mapping in the injector, right?
    I use Injector.addInjectionPoint to define these injection points for my classes. Again, more faithful to the original. It has the benefit of being able to name the properties in your injectees as you want, but obviously adds an extra step for each property.

  10. 11 Posted by Jeremy Ruppel on 21 Apr, 2011 05:24 PM

    Jeremy Ruppel's Avatar

    @creynders, thanks. The first couple attempts I made at the library were more 'faithful' to the RL we know and love, but I kept having doubts as to whether or not javascript devs would actually use it. Let me explain what I mean.

    From your description, it sounds like a developer may have to configure an injection point like this (and please excuse the pseudo-code):

    window.MyActor = (Class definition here...)
    
    window.AnotherActor = (Class definition here...)
    
    function startup( )
    {
        injector.mapClass( window.MyActor, window.MyActor );
    
        injector.addInjectionPoint( window.AnotherActor, window.MyActor, 'foo' );
    }
    

    This would presumably create an injection point on AnotherActor which will inject an instance of MyActor under the property name foo. My fear is that the typical javascript developer won't use the injector, but rather create the dependency manually because it's available, like:

    window.MyActor = (Class definition here...)
    
    window.AnotherActor = function( )
    {
        this.foo = window.MyActor( );
    }
    

    Why would they use the injector? What is the benefit to someone that doesn't understand its purpose? One could argue the second approach is actually more readable.

    My current approach in legs-js is a bit different. It's not immediately obvious from the tests, but the implementation I chose effectively restricts the developer from making those sort of references. When setting up a context, all actors and commands are defined in one object, during whose time of creation does not allow its members to reference eachother. It ends up looking like:

    var Context = Legs.Context.extend(
        {
            actors :
            {
                MyActor : (Class definition here...),
    
                AnotherActor : (Class definition here...)
            }
        } );
    

    If you were to try to reference actors.MyActor in actors.AnotherActor, you will get an error. The issue really stems from the fact that we don't have to worry about these sort of global references in AS3. In javascript, we totally do.

    Similarly, with this approach, just as you can't reference other actors directly, you also can't reference their class to create a traditional dependency by class. This was tough for me to swallow, but that's the real reason about treating all injection points as named and using strings. I'd rather encourage the use of the injector and give a helpful error if the developer specifies an incorrect name than have them avoid the injector altogether.

    One more point I'm attempting to make with this new version is to embrace javascript as the domain in which we're working, for all its strengths and faults, and especially for familiarity of the community. For example, when deciding on an API for the event bus, consider these two interfaces:

    // the actionscript way
    eventDispatcher.addEventListener( *eventType*, *eventHandler* );
    eventDispatcher.dispatchEvent( new Event( *eventType*, *...payload values* ) );
    
    // the jquery way
    events.bind( *eventType*, *eventHandler* );
    events.trigger( *eventType*, *...payload values* );
    

    I feel like javascript devs will embrace the latter because of terseness and familiarity. With this in mind, we have to ask ourselves what it is we're actually trying to accomplish. Are we trying to translate to javascript the RL code or ideas?

    Definitely looking forward to seeing your WIP on GitHub. Please post back when you've pushed it up. In the meantime, one other library I thought you might be interested in is a jquery plugin called livequery. In my first attempts with legs-js, I used it as an analogy to our ADDED_TO_STAGE event to help out my MediatorMap.

    Cheers,

    J

  11. Support Staff 12 Posted by creynders on 22 Apr, 2011 06:40 AM

    creynders's Avatar

    Thanks for the explanation. I hadn't realized you can't instantiate mapped classes directly.
    TBH though I don't get your reasoning for this.

    Why would they use the injector? What is the benefit to someone that doesn't understand its purpose?

    If a developer doesn't understand the benefit/purpose of using a DI framework, why would they use it at all? And in the other direction, if a developer chooses to use a DI framework, why would they NOT use it for what it's meant to do? I don't get it. That has nothing to do with it being written in JS, has it?

    There's also one major disadvantage in this approach and that's its limitations on reusability. If you want to reuse a class from another project (which is one of the main goals of using DI altogether) then you'll be refactoring its members if they don't match the already mapped names in your current project. Or vice versa you'll be refactoring another actor in your current project to match the member names of the actor you're reusing from another project.
    Another disadvantage is that all your actors get tied to your DI framework. If you would want to reuse a class from a project that didn't use this framework, you'll need to refactor it so it can be used by this framework.

    Sorry, I don't want to sound too negative, it's just my 2 cents, obviously. I'm no expert on DI frameworks and certainly not on JS either.

    About the messaging system:
    It makes sense to have bind and trigger, that's exactly the kind of change that makes sense when porting it to JS. JS developers will definitely feel more at home and to devs coming from RL it will be a minor adjustment.

    I wrote my own eventdispatcher, but I think I'll swap it for the JS signals implementation of Millermedeiros:
    http://millermedeiros.github.com/js-signals/

    Now a little bit of explanation on Dijon (current name of my framework).
    The goals I set out for myself when starting to develop Dijon were the following:

    • Framework independent: no JQuery, Prototype, etcetera. Chances are people will be using it faster if they don't need to switch their favorite framework
    • Mapped and injected classes should not be necessarily aware of the framework
    • Recognizable enough for people coming from RL, but adjusted to JS

    I hope to push it today, I'll keep you updated!

  12. Support Staff 13 Posted by creynders on 22 Apr, 2011 09:43 AM

    creynders's Avatar

    I pushed it to github. As I said, this is not working, but you get an idea of the direction I'm taking.

    https://github.com/creynders/dijon-framework

  13. 14 Posted by Aaron Hardy on 24 Sep, 2011 03:04 AM

    Aaron Hardy's Avatar

    Jeremy, I'm curious to know why you feel that mediators are out-of-place in JavaScript. I'm considering that theory myself, but the way I see it, the purpose of a mediator is to decouple my view (which includes both presentation and view logic) from other framework actors. Without a mediator, you lose this decoupling. Generally I have a JavaScript object that manages associated DOM elements. I see the DOM being presentation (similar to MXML in a MXML component) and the backing JavaScript object as the view logic (similar to the ActionScript in a MXML component). I don't want actors in my JavaScript view-managing objects so building a mediator would do what a mediator does in RL keeping my JavaScript view-managing object decoupled so I can re-use it in other places or switch the micro-architecture I'm using. Thoughts?

  14. Ondina D.F. closed this discussion on 23 Dec, 2011 08:52 AM.

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