Service - Promise/Asynctoken Handling

ego's Avatar

ego

12 May, 2011 01:58 PM

Hi Smartfolks,

I'm just in a big discussion with my workmates how to set up our service handling and maybe someone has an opinion to my thoughts.

In the first place the decision was made to handle service calls via tokens/promises and not via events. I think this approach is much cleaner when it comes to concurrent service calls and so forth.

The only thing that I do not understand is why most examples and even some framework implementations layout their calls like this:

function serviceCall(query:String):AsyncToken

wouldn't it be better like this

function serviceCall(responder:IResponder, query:String):void

Meaning that a responder object is always handed in the method.

With this approach you would stay more flexible how to handle the call internally in the service. Maybe there is some client side caching active and the call does not have to be handled async at all. When handing out the token as a returnvalue this would be impossible without any nasty delay hacking or similar.

Any thoughts on this? Thanks!

  1. 1 Posted by Stray on 12 May, 2011 02:08 PM

    Stray's Avatar

    Isn't the point of the AsyncToken that it's handed out instantly, but won't be populated til later? It's essentially a "promise" of some results to come.

    The caller then binds to the Token, waiting for the results / errors etc.

    Or have I misunderstood something?

    The reason for the service creating the AsyncToken is that you encapsulate the knowledge and responsibility for *creating* the AsyncToken in one place - where as if you pass it in then multiple users have to know how to create (as well as use) the AsyncToken. If you want to bind to an abstract or interface type instead of a concrete type then this is important.

    But ... I might be missing something in your question.

    Stray

  2. 2 Posted by ego on 12 May, 2011 02:27 PM

    ego's Avatar

    Yes in theory this totally right and I didn't see any problems with that so far. The question came to me when I was practically thinking about client side caching.

    When if is this "later" is just a "now"? I just can't do it without a somehow nasty delay handling inside my service.

    But still... I would not create the actual asynctoken outside. Just a responder Class (mapped to an interface), which then internally gets linked to a token.

    I don't know ... maybe I have to stick to a pattern. But I don't like if I have to follow something that seems to be limiting me.

    but ... I might missing the point ;)

    chris

  3. 3 Posted by Hannes on 12 May, 2011 03:58 PM

    Hannes's Avatar

    Hello,

    I think giving back a token like the AsyncToken from Flex could be quite a dangerous design decision.

    First of all you should have the possibility to do some work between receiving the result and sending it back to the client, like parsing the result object to a client aware instance or using some cache mechanism.

    Another thing is the clear separation of concern. The "client" like a command should never get information about infrastructure details, because it should not be part of the infrastructure layer. It has completely different responsibilities.
    If you take a look at the mx.rpc.AsyncToken you can see that this class provides a lot of infrastructure based data.
    Why should a command be aware of it?

    It is really interesting that a lot of frameworks like cairngorm, parsley, ... are talking about layers, separation of concern etc., but nobody of them is doing it the right way in my opinion.

    Hannes

  4. 4 Posted by ego on 13 May, 2011 09:27 AM

    ego's Avatar

    hannes answer makes perfect sense to me.

    i would opt for a similar call like this:

    `function serviceCall(responder:IResponder, query:String):void

    I'm just wondering if I'm digging in some weird scenarios and nobody else was thinking about this before... as Hannes mentions: The Parsley Framework Doku kinda recommends this approach with handing out an asynctoken to a command.

  5. 5 Posted by Stray on 13 May, 2011 09:33 AM

    Stray's Avatar

    I'm obviously missing something because I can't see the different except in who has responsibility for creation.

    I'd use promises (check out Shaun's oil utils) instead of the AsyncToken - but the purpose of the token is that it simply represents a way to get the result in the future. Whether the response is synchronous or asynchronous shouldn't matter - the promise should be smart enough to deliver its result instantly if the handlers are added afterwards (which I guess is your concern?)

    That said, I tend to go for the standard event approach myself, so maybe I'm not digging the detail?

  6. 6 Posted by ego on 13 May, 2011 10:18 AM

    ego's Avatar

    "the promise should be smart enough to deliver its result instantly if the handlers are added afterwards (which I guess is your concern?)"

    correct! is this would be a concrete solution for what I was actually lookin for... and actually one way to answer my question: "Smarten up your tokens!"

    I will think about it! :)

  7. Support Staff 7 Posted by Stray on 13 May, 2011 11:18 AM

    Stray's Avatar

    I'm glad that made sense!

    Part of the problem is that we suffer availability-bias in our thinking due to the language constructs, when really our intention is important.

    When we do something like:

    dataDeliveryToken.addEventListener ( DataEvent.DATA_DELIVERED,
                                     dataDeliveredHandler );
    

    we think we're listening for an Event. But actually, from an intent point of view, we're saying:

    dataDeliveryToken.notifyMeWhenDataIsDelivered_using (           
                                                dataDeliveredHandler) ;
    

    Now - if the data has already been delivered, it's only sensible that the asyncItem would say "Oh, actually it's here already - here you go!" and run the handler immediately.

    Order of operations and race conditions problems are issues of language / compiler / runtime detail - it's annoying that we have to work around them sometimes, but I think the starting point should always be "What's my intent? What makes sense? How would I describe this relationship / process / request in words?" and then work from there.

    It's one of the reasons why it's nice to work in a few different languages - it starts to show you what hoops the specific language makes you jump through!

    Stray

  8. 8 Posted by Fabien on 13 May, 2011 11:51 AM

    Fabien's Avatar

    Is there a Promise implementation for RemoteObject ?

  9. 9 Posted by Fabien on 13 May, 2011 12:34 PM

    Fabien's Avatar

    @Stray

    How do you manage command sequencing if you dispatch events from your services ?
    I tried to follow your service/dispatchEvent way, but I get stuck when using services calling commands which are used in a startup sequence and also independently on user actions.
    Example :
    I want to get a list of albums a user own at startup, among other sequenced requests and also use this command when a user click to see his friend's albums.

  10. 10 Posted by Tony Smith on 16 May, 2011 06:44 AM

    Tony Smith's Avatar

    This might be a product of convenience but I have always just implemented the IResponder interface on my service-calling command. Then, as ego seems to do, I send a reference of the command to the service. Then the command handles the result/fault. From here I can operate on the injected model or chain other commands to the response.

    In terms of caching, my commands sometimes check for the existence of data (model or entity cache manager) to determine whether to call the service to just call the result handler directly with cached data. You can also throw in some event properties to force a refresh of cached data.

  11. Support Staff 11 Posted by Stray on 25 May, 2011 08:20 AM

    Stray's Avatar

    @Fabien

    sorry - missed your response there.

    I'm not sure I've had to solve the specific problem you're describing, but I do use a command queuing util (DeferredCommandMap) which is solving a similar problem for me.

    My 'similar' problem is that I have an administrator tool which is used to edit remote data. The tool can do many different things, and usually the administrator only wants to do one task. There are multiple tabbed screens that each represent a task "Manage users / manage groups " etc. Most screens require more than one data set, and many of the screens have overlaps in what they require.

    The user manually initiates loading for the screen they want to use (they click a big button saying "load data for this screen) and then all the required data types are queued for loading.

    Does that sound similar?

    Stray

  12. 12 Posted by Fabien on 25 May, 2011 11:15 AM

    Fabien's Avatar

    Hi Stray,

    I looked up DeferredCommandMap code but I don't think it covers my needs. I need a sequence to stop in case of an error in the service or a result.
    Typically, what I want is :
    - I validate the user session - then I log the user (I could merge with the above) - then I start loading specific stylesheets bundles and modules - then I get some data from the server

    Moreover, I want to display a friendly error message in case any of these return a fault or an invalid result, and stop the sequence here.

    In order to do that I transformed all my Commands into AsyncCommand and I handle the result/fault in the command.
    Example :

    // ValidateUserSessionCommand extends AsyncCommand
    [Inject]
    public var event:UserEvent;
    
    override public function execute():void
    {
         trace(this, "execute");
         service.validateUserSession(event.userVO.id)
               .addResponder(new Responder(onResult, onFault));
    }
    
    private function onResult(event:ResultEvent):void
    {
        var success:Boolean = remoteResultProcessor.processResult(event);
    
        if(success)
        {
            //  update model..
        }
    
        dispatchComplete(success);
    }
    
    private function onFault(event:FaultEvent):void
    {
        remoteResultProcessor.processFault(event);
        dispatchComplete(false);
    }
    
    override public function dispatchComplete(success:Boolean):void
    {
        if(success)
        {
            trace(this, "complete");
        }
        else
        {
             trace(this, "error");
            dispatch(new ErrorPopupEvent(ErrorPopupEvent.POP_ERROR, "Friendly error message..));
        }
        super.dispatchComplete(success);
    }
    

    From there, I use Macrobot's SequenceCommand :

    // StartupCommand extends SequenceCommand
    override public function execute():void
    {
        trace(this, execute);
        this.atomic = false;
    
        var userVO:UserVO = new UserVO();
        userVO.id = appParams.userId;
    
        var userEvent:UserEvent = new UserEvent(UserEvent.VALIDATE_USER_SESSION, userVO);
        this.addCommand(ValidateUserSessionCommand, userEvent, UserEvent); 
    
        userEvent = new UserEvent(UserEvent.GET_USER, userVO);
        this.addCommand(GetUser, userEvent, UserEvent);
    
        this.addCommand(LoadStyleSheet, "shared", String);
        this.addCommand(LoadResourceBundle, "shared", String);
    
        super.execute();
    }
    

    I have sequence + standalone reusable commands in other part of the app. Though it's not the best architecture, it feels nice to me... but before you bring out the whip, let me say I am opened to anything better :p

  13. Support Staff 13 Posted by Stray on 07 Jun, 2011 01:24 PM

    Stray's Avatar

    No whip! No whip!

    Yes - you're using your commands as a macro where each command is dependent on the success of the previous one. It's not a problem I've had to solve so I'd say that if your solution is working then it's valid!

  14. 14 Posted by hipitihop on 30 Sep, 2011 01:55 AM

    hipitihop's Avatar

    I realise this is Robotlegs but we can always learn from other places. I have found a nice implementation in PureMVC, apart from a normal SimpleCommand and MacroCommand, there is also AsyncMacroCommand where elements are AsyncCommand. Instances are required to call commandComplete() which lets the macro command know when it has completed, and the next command can continue. If an AsyncCommand fails, it doesn't call commandComplete() and execution stops at that point in the chain. see http://puremvc.org/pages/docs/AS3/Utility_AS3_AsyncCommand/asdoc-st...

  15. Support Staff 15 Posted by creynders on 30 Sep, 2011 05:52 AM

    creynders's Avatar

    In case you're using signals you could also try out my SignalResponder class
    https://github.com/creynders/SignalResponder
    (I'm sorry for the blatant self-promotion, but I wrote it because I had use for it, maybe others do too) Unfortunately at the moment you'll have to pass the signalresponder instance to the service instead of letting the service create it, since signals aren't "smart enough to deliver its result instantly if the handlers are added afterwards"
    You could however use it in combination with my RelaxedSignals addition to the Signals lib:
    https://github.com/creynders/as3-signals
    I'm trying to get it pulled into the official lib, but have had no response from Robert Penner yet.

    -EDIT- Hmmm, this is an old thread. I didn't notice that when I wrote the reply.
    Tender works in mysterious ways.

  16. 16 Posted by Stray on 30 Sep, 2011 07:22 AM

    Stray's Avatar

    Ah! Awesome - I had started work on a relaxed signal - but you're already there. Super!

  17. Support Staff 17 Posted by creynders on 30 Sep, 2011 05:19 PM

    creynders's Avatar

    @stray If you got the time, all feedback on the RelaxedSignals is very welcome!

  18. Support Staff 18 Posted by creynders on 07 Oct, 2011 12:58 PM

    creynders's Avatar

    If anybody lands here looking for info on RelaxedSignals, here's a link to the demo:
    http://www.creynders.be/2011/10/06/relaxedsignals/

  19. Ondina D.F. closed this discussion on 16 Nov, 2011 09:22 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