Chaining Startup Commands/Services

rivella50's Avatar

rivella50

11 Mar, 2014 09:51 PM

Hi there,

i just switched to RL2. Normally in an app i have to perform some tasks on startup in an ordered sequence (e.g. Determine the correct locale, login at the server, ...). For each task i have a command which just invokes a corresponding service which does the actual work.
Now my questions:
1) How can the chaining of these ordered commands/services be performed, i.e. which RL component will receive the finished notification signals of every service and decide what to do next (invoke another command or inform the mediator which shows the first screen) ?

2) I don't think it's a good idea if every service knows about the next step in the mentioned startup sequence, although it would probably work with an injected state VO which holds a flag about the current application state (e.g. "startup", ...).
The better way is in my opinion if every service just dispatches a notify signal after finishing its work and with that it stays independent and can also be used as a single service, if needed.

I would be very interested how you'd implement such a startup chaining behaviour with RL 2 and above all which component acts as the coordinator. Thank you very much.

Best regards
valley

  1. Support Staff 1 Posted by Ondina D.F. on 12 Mar, 2014 10:09 AM

    Ondina D.F.'s Avatar

    Hi Valley,

    I don't think it's a good idea if every service knows about the next step in the mentioned startup sequence

    You're right, services shouldn't have to know about the flow of your loading process or the state of your app.

    The better way is in my opinion if every service just dispatches a notify signal after finishing its work and with that it stays independent and can also be used as a single service, if needed.

    Exactly.

    How can the chaining of these ordered commands/services be performed

    I'll give you a list of utilities/ extensions that may be of use to you:

    DirectCommandMap:

    https://github.com/robotlegs/robotlegs-framework/tree/master/src/ro...

    Macro command utility for Robotlegs 2 which provides the ability to execute batches of commands in sequential or parallel fashion:

    https://github.com/alebianco/robotlegs-utilities-macrobot

    AS3StateMachine:

    https://github.com/AS3StateMachine

    DeferredCommandQueue:

    https://github.com/DavidWhittingham/robotlegs-utilities-DeferredCom...
    https://github.com/DavidWhittingham/robotlegs-extensions-SignalStat...

    SignalResponder:

    https://github.com/creynders/SignalResponder
    https://github.com/creynders/RelaxedSignalsDemo

    Bootstrap:

    https://github.com/creynders/robotlegs-demo-bootstrap

    Promises:

    https://github.com/darscan/robotlegs-extensions-Oil

    http://knowledge.robotlegs.org/discussions/questions/115-using-the-...
    There are more discussions on this topic on this forum. Search for 'Promises'.

    https://github.com/jonnyreeves/as3-async

    https://github.com/CodeCatalyst/promise-as3

    https://gist.github.com/darscan/4519372

    Ondina

  2. 2 Posted by rivella50 on 13 Mar, 2014 08:04 AM

    rivella50's Avatar

    Great, thank you Ondina.
    I will have a look at the projects you mentioned.
    Hopefully there are some approaches which support chaining commands AND
    the combination of a command which invokes a service...

    valley

  3. Support Staff 3 Posted by creynders on 14 Mar, 2014 12:46 PM

    creynders's Avatar

    Hey Valley,

    Macrobot does exactly what you want, it allows creating command chains. You can mix and match synced and async operations as you please.

    For more complex flows I'd definitely recommend a state machine.

  4. 4 Posted by rivella50 on 14 Mar, 2014 09:08 PM

    rivella50's Avatar

    Hi creynders,

    with RL2 and Macrobot i would use AsyncCommand's to perform stuff like login at a remote server or download some data, and not that the command invokes a service which does that work ? Or how would that work properly, with a callback proxy ?

    And do i understand it correctly that an AsyncCommand handles the detain/release part transparently and i only need to implement the listeners and the final complete dispatch signal ?

    Thank you.
    valley

  5. Support Staff 5 Posted by creynders on 15 Mar, 2014 07:14 AM

    creynders's Avatar

    No, the asynccomand calls the service and listens to its completion. Then it dispatches its complete event.
    Something like this:

    public function execute():void{
        service.addEventListener(ServiceEvent.FINISHED, finishedHandler);
        service.doYourThing(); 
    }
    private function finishedHandler(result){
        dispatchComplete(true);
    }
    

    That's all there is to it really. Personally I use signals or promises for messaging from services to commands and only use events for system-wide messaging. But the service could also dispatch a system event in which case you don't let the command listen to the service directly but to the injected eventDispatcher.

  6. 6 Posted by rivella50 on 16 Mar, 2014 07:54 PM

    rivella50's Avatar

    Works like a charm, thank you very much, creynders!
    I'm using the signal approach to let the command get notified about the completed service.
    Since i mapped the service as follows i had to cast the interface to the service class for getting access to the signal in the command, but this should be no problem i guess:

    injector.map( IServiceCommandA ).toSingleton( ServiceCommandA );
    
    and in the command:
    [Inject]
    public var service:IServiceCommandA;
    ...
    ServiceCommandA(service).finishedSignal.add(completeHandler);
    
  7. Support Staff 7 Posted by creynders on 16 Mar, 2014 09:38 PM

    creynders's Avatar

    I wouldn't upcast, but rather add a getter for the signal to the iface.

    On Sunday, March 16, 2014, valley <
    [email blocked]> wrote:

  8. Support Staff 8 Posted by creynders on 17 Mar, 2014 10:50 AM

    creynders's Avatar

    Hey Vallye,

    I'll expand a little on why I wouldn't upcast. Upcasting is always a bit fishy and a code smell (except in a few very rare cases). The problem is that you couple the command tightly to the concrete service class, which means that if you need to swap service classes you'll need to update your command as well. On top of that if the original service class remains in the code base and you forgot to update the command, then this will not generate a CTE, but only fails at run-time. Potentially pretty dangerous.

    There's a few approaches you can take:

    1/ Drop the interface and map the concrete service instead. Least preferable.
    2/ Add a getter for the signal to the interface and concrete service class.
    3/ Extend the original interface in a new interface which exposes a getter to the signal and use this new interface instead.

    I'd definitely go with 2/ in almost all cases. It's one of the benefits of signals that you can add your messaging mechanism to your interface. I'd only use 3 if I'm using an application-specific wrapper for a more generic interface/class.

  9. 9 Posted by rivella50 on 17 Mar, 2014 07:14 PM

    rivella50's Avatar

    Ok, i think you are right, thank you for your thoughts.
    I will choose one of points 2) and 3) for my concrete app.

    Regards
    valley

  10. 10 Posted by rivella50 on 25 Mar, 2014 09:35 PM

    rivella50's Avatar

    Now i just ran into an error which i couldn't solve quickly.
    Let's say we have an authentication mechanism with a command and a related service, where the command registers to be notified on the service's complete action.
    Since the service is handled as a Singleton with

    injector.map( IAuthenticateUserService ).toSingleton( AuthenticateUserService );
    
    the offered signal to the command stays there as well.
    Now if the command runs several times it registers again and again as a listener for that complete signal and will be notified more than once when the command is invoked several times:
    public class AuthenticateUserCommand extends AsyncCommand {
            [Inject]
            public var authenticateService:IAuthenticateUserService;
            [Inject]
            public var notifySignal:NotifyUserAuthenticatedSignal;
            override public function execute():void {
                authenticateService.doAuthenticate();
                authenticateService.getNotifyCommandSignal().add(onCompleteService);
            }
            private function onCompleteService(statusVo:StatusVO):void {
                notifySignal.dispatch(statusVo);
            }
    }
    
    Therefore i'm removing now all listeners for the service signal in function onCompleteService with:
    authenticateService.getNotifyCommandSignal().removeAll();
    
    I'm not sure if this is the best way to do this, but now it works fine.
    Another possibility could be not to use Singleton services but services which will be instantiated again on every new run?
  11. Support Staff 11 Posted by creynders on 26 Mar, 2014 06:38 AM

    creynders's Avatar

    Another approach would be to use addOnce: authenticateService.
    getNotifyCommandSignal().addOnce(onCompleteService);

  12. Ondina D.F. closed this discussion on 28 Apr, 2014 08:13 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