Lifetime of Services and Commands, or weird Service behavior

Gerry Koh's Avatar

Gerry Koh

04 Jan, 2011 06:35 PM

Hello,

I have a command that calls a method on an injected service and listens for the result event. I am observing that the first time the command is fired, the event handler is called once. the second time the command is executed, the event handler is called twice. the third time, its called three times. I am wondering about the lifetimes of commands and services? AFAIK commands are very short lived, and i'm assuming services are long lived so the same instance can be injected whenever necessary. I suspect that the framework creates a single eventDispatcher that it injects everywhere, so i tried removing the event listener before adding in case i was just adding additional event listeners. The Service just makes some REST calls and sends a single event upon success.

thanks in advance for any assistance!

g

here is the command code:

    // Public Interface
    //
    [Inject]
    public var event:PersistVideoEvent;

    [Inject]
    public var service:ICdnService;

    [Inject]
    public var model:ComposerModel;


    // Overrides
    //
    override public function execute():void
    {
        var video:VideoAsset = model.currentVideo;
        service.eventDispatcher.removeEventListener(VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED,onVideoDetailsUpdated);
        service.eventDispatcher.addEventListener(VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED,onVideoDetailsUpdated);
        service.updateVideoDetails(video.id,video.startDate,video.endDate,video.name,video.descriptions,video.actions);
    }


    // Event Handlers
    //
    protected function onVideoDetailsUpdated(event:VideoDetailsUpdatedEvent):void
    {
        if (event.video.id == model.currentVideo.id)
        {
            // success
            Alert.show("Video has been successfully updated","Success",Alert.OK);
        }
        else
        {
            // fail
            Alert.show("Video was not successfully updated","Fail",Alert.OK);
            logger.error("Attempt to update video details failed");
        }
    }
  1. 1 Posted by krasimir on 04 Jan, 2011 07:23 PM

    krasimir's Avatar

    Hello,

    I don't have great experience with robotlegs, but I think that the problem that you described occurs because you are mapping the service as a singleton. In this case when you inject your service you will access the same instance of that class. Or in other words you are adding the same listener several times, every time when the command is executed. So the question is how did you map your service into the context.

    By the way, you can use hasEventListener instead of removeEventListener.

  2. 2 Posted by Gerry Koh on 04 Jan, 2011 07:30 PM

    Gerry Koh's Avatar

    Yes, I'm mapping the service as a Singleton:

            injector.mapSingletonOf(ICdnService,OoyalaService);
    

    (btw, what does the Of do? I've just copied examples using it and it works)

    I'm assuming that both the service and especially its eventDispatcher stick around for a while, which is why i try to remove existing event listeners first.

    A thought just occurred to me: if the command itself is short lived, then adding an event listener from two instances of the command might result in two separate listeners on the eventDispatcher, which might result in the event being dispatched twice and both caught by the current command instance.

    i am going to try removing the event listener in the event handler, when the command instance is still the same. maybe using weak references would help too. i'll report back, thanks!

    g

  3. 3 Posted by Gerry Koh on 04 Jan, 2011 07:37 PM

    Gerry Koh's Avatar

    that was totally it! i don't know the details of how eventDispatchers work, but i'm guessing that having two separate command instances added to its listeners array somehow caused it to fire two events on the same bus, which were received by the current command twice. removing the event listener in the event handler solved the problem, and i suspect weak references would too, but that might depend of garbage collection timing.

  4. 4 Posted by Gerry Koh on 04 Jan, 2011 07:38 PM

    Gerry Koh's Avatar

    here is the fixed code:

        // Public Interface
        //
        [Inject]
        public var event:PersistVideoEvent;
    
        [Inject]
        public var service:ICdnService;
    
        [Inject]
        public var model:ComposerModel;
    
    
        // Overrides
        //
        override public function execute():void
        {
            var video:VideoAsset = model.currentVideo;
            eventDispatcher.addEventListener(VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED,onVideoDetailsUpdated);
            service.updateVideoDetails(video.id,video.startDate,video.endDate,video.name,video.descriptions,video.actions);
        }
    
    
        // Event Handlers
        //
        protected function onVideoDetailsUpdated(event:VideoDetailsUpdatedEvent):void
        {
            eventDispatcher.removeEventListener(VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED,onVideoDetailsUpdated);
            if (event.video.id == model.currentVideo.id)
            {
                // success
                Alert.show("Video has been successfully updated","Success",Alert.OK);
            }
            else
            {
                // fail
                Alert.show("Video was not successfully updated","Fail",Alert.OK);
                logger.error("Attempt to update video details failed");
            }
        }
    
  5. Gerry Koh closed this discussion on 04 Jan, 2011 07:38 PM.

  6. krasimir re-opened this discussion on 04 Jan, 2011 07:41 PM

  7. 5 Posted by krasimir on 04 Jan, 2011 07:41 PM

    krasimir's Avatar

    Can you please give more details about your service class. About ICdnService and OoyalaService. Are they extending something.
    If you ask me, I'll add the listener for VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED in the body of the Service, not outside it. I don't know the structure of your application but for me it looks much much better if you place the listeners there and connect them with private functions.

    From the documentation:
    mapSingleton provides a single instance of the requested class for every injection. Providing a single instance of a class across all injections ensures that you maintain a consistent state and don’t create unnecessary instances of the injected class. This is a managed single instance, enforced by the framework, and not a Singleton enforced within the class itself.

    mapSingletonOf is much like mapSingleton in functionality. It is useful for mapping abstract classes and interfaces, where mapSingleton is for mapping concrete class implementations.

  8. 6 Posted by Paul Robertson on 04 Jan, 2011 07:47 PM

    Paul Robertson's Avatar

    The "Of" in mapSingletonOf() differentiates between a singleton that is
    mapped as an interface implementation versus a singleton that isn't
    mapped to an interface.

    The difference is in what data type you use for your [Inject] properties
    -- if you want to use the concrete data type, use mapSingleton(), if you
    want to specify an interface use mapSingletonOf().

    (For services I would always use an interface, because it makes it easy
    to swap them out for testing or if the implementation changes -- both of
    which I've personally encountered.)

    Back to your general question, I think the crux of the issue is that
    Commands aren't meant to have state, including doing event handling.
    (There are several interesting variations on Commands that do handle
    events, but that's not the "standard" MVCS implementation.)

    In a typical MVCS implementation, the command calls the service and
    that's the end of the command. The service has its own event handlers
    for its own events. When the service result returns, it dispatches an
    event indicating success or failure, including the relevent data.

    That way, any actions that should be triggered by the result/failure of
    the service can be mapped to the event that the service dispatches. For
    example, if you want to update a model based on the service call, you
    can have a command mapped to the event that the service dispatches and
    update the model from there. If you want to display some data or change
    the view, you can have a mediator listen for that event and notify the
    view. (Or the mediator can listen for a change event dispatched by the
    model and use that to trigger the view change.)

    I realize the Alert in the event handlers is probably just for testing,
    but I would personally try to stay away from that since Alert is clearly
    a view-related thing.

    Paul

  9. 7 Posted by Gerry Koh on 04 Jan, 2011 08:07 PM

    Gerry Koh's Avatar

    the service implements Actor, and ICdnService is just its interface. i use interfaces for services so that must be why i've always used mapSingletonOf. I don't need any listeners inside the service except for HTTPService listeners, and i don't want to pass those events on to the application for coupling reasons. I prefer to use events rather than callbacks to interface the service to the rest of the application, but am always open to better ways to do things!

    here is the service call code. it is basically 4 or 5 REST calls chained together, with the final result handler dispatching the success event:

        public function updateVideoDetails
            (
                embedCode:String,
                startTime:Date=null,
                endTime:Date=null,
                name:String=null,
                descriptions:ArrayCollection=null,
                actions:ArrayCollection=null
            ):void
        {
            sendRequest(OoyalaServiceConstants.EDIT_HOST,parameters,retrieveExistingActions);
    
            function retrieveExistingActions(event:ResultEvent):void
            {
                if (event.statusCode == 200)
                {
                    sendRequest(OoyalaServiceConstants.QUERY_HOST,parameters,removeExistingActions);
                }
                else
                {
                    throw new Error("Attempt to update video attributes failed");
                }
            }
    
            function removeExistingActions(event:ResultEvent):void
            {
                if (event.statusCode == 200)
                {
                        sendRequest(OoyalaServiceConstants.SET_METADATA_HOST,parameters,deletionResultHandler);
                }
                else
                {
                    throw new Error("Attempt to retrieve video actions failed");
                }
    
                function deletionResultHandler(event:ResultEvent):void
                {
                    if (event.statusCode == 200)
                    {
                            addActions();
                    }
                    else
                    {
                        throw new Error("Attempt to remove existing actions failed");
                    }
                }
            }
            function addActions():void
            {
                    sendRequest(OoyalaServiceConstants.SET_METADATA_HOST,parameters,resultHandler);
            }
    
            function resultHandler(event:ResultEvent):void
            {
                if (event.statusCode == 200)
                {
                    dispatch(new VideoDetailsUpdatedEvent(video));
                }
                else
                {
                    throw new Error("Attempt to add actions to video failed");
                }
            }
        }
    
  10. 8 Posted by Gerry Koh on 04 Jan, 2011 08:11 PM

    Gerry Koh's Avatar

    Paul,

    I see your point about the lifespan of commands. I'm open to listening for service events directly in the appropriate mediator rather than the command itself. it seemed to me that given the choice to couple a command to a service or a view mediator to a service that the command would be the better choice, but i suppose a service interface change will require a change somewhere in the app anyway.

    thanks!

  11. 9 Posted by Paul Robertson on 04 Jan, 2011 09:09 PM

    Paul Robertson's Avatar

    Like you I don't usually like to dispatch service-specific events into
    my app. Typically I do one of two things with my services:

    1. Create a custom event containing the result data, fault info, etc.
    and dispatch an instance of that from the service. That way I'm not
    tying my app to the specific event used in the service unless I really
    like it for some reason.

    2. Inject my model into the service, and use the service result to set a
    value on the model directly. Setting the value on the model then
    dispatches a change event that is picked up by any interested parts of
    the code such as mediators. This couples my service to my model, so some
    people (myself included at times) would discourage this. This is where
    you get into judgment calls about "appropriate" coupling.

    Paul

  12. 10 Posted by Abel de Beer on 04 Jan, 2011 10:39 PM

    Abel de Beer's Avatar

    Switch to AS3Signals! It makes your code much more elegant.

  13. 11 Posted by Gerry Koh on 05 Jan, 2011 12:34 AM

    Gerry Koh's Avatar

    Abel, I have been trying to switch to Signals :) I downloaded the core project and another one that provided a SignalContext or something, got errors, and got no responses to my posts on here.

    You could help me greatly by pointing me to the repositories of the projects needed to add signals support into my application, and please make a note of which versions work for you, when I had played with it the head revisions in the master branch did not work for me.

  14. 12 Posted by Gerry Koh on 05 Jan, 2011 12:34 AM

    Gerry Koh's Avatar

    thanks paul,

    those are good options.

  15. 13 Posted by Nikos on 06 Jan, 2011 01:16 PM

    Nikos 's Avatar

    I fail to see the point of this since the command will be discarded

    // Event Handlers

    //
    protected function onVideoDetailsUpdated(event:VideoDetailsUpdatedEvent):void
    {
        eventDispatcher.removeEventListener(VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED,onVideoDetailsUpdated);
    
  16. 14 Posted by Gerry Koh on 06 Jan, 2011 05:26 PM

    Gerry Koh's Avatar

    nikos, when you execute a command, insert a breakpoint and inspect eventDispatcher. it has an array of listeners. since commands are short lived, each command has a different object id and occupies a different spot in the listeners array.

    i suspect that by not removing the listener in the same command instance that created it results in extra entries in eventDispatcher.listeners.

    i don't know enough about the specifics or the robotlegs event system to know why that resulted in my event handler being called once for each extra entry in the listeners array, but when i moved the removeEventListener call to the same command instance that registered it, the problem went away.

  17. 15 Posted by Nikos on 06 Jan, 2011 05:35 PM

    Nikos 's Avatar

    see this thread, maybe you have extra mediators?
    http://knowledge.robotlegs.org/discussions/questions/134-how-do-i-k...

  18. 16 Posted by Abel de Beer on 06 Jan, 2011 07:20 PM

    Abel de Beer's Avatar

    @Gerry: I use the following versions of the core libraries / extensions in my latest Signals enabled project:

    Robotlegs v1.4.0 / AS3Signals v0.8 / Joel Hooks' Signals extension for Robotlegs

    You should add the Robotlegs and AS3Signals SWC files to your classpath and add the files inside the extension's 'src' folder to your own 'src' folder. To be able to access the signalCommandMap, your Context should extend SignalContext. Let me know if you can get it to work.

  19. Stray closed this discussion on 11 Feb, 2011 11:04 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