Page 1 of 2 12 LastLast
Results 1 to 10 of 14

Thread: question about an assumption behind Operations

  1. #1
    Join Date
    Feb 2010
    Posts
    6

    Default question about an assumption behind Operations

    Hello.

    I was excited to download and play with Spring Actionscript 1.0. I like the improvements, and I LOVE having documentation to help me better understand the Operation/Command Framework. Good work.

    I wanted to ask for your reasoning behind one design decision - and my question might be naive as I'm relatively new to Flex.

    It appears that Operation constructors must NOT return their results immediately. Here's GenericOperationCommand.execute():
    Code:
    			_operation = ClassUtils.newInstance(_operationClass,_constructorArguments);
    			_operation.addCompleteListener(operationComplete);
    			_operation.addErrorListener(operationError);
    If the operation's constructor immediately (i.e., synchronously) dispatches a completion or error event, the listeners will not receive this event.

    I've thought of a couple of reasons why I might want operations to return immediately:
    • Testing. I wanted to create a MockOperation that failed consistently, 100% of the time. But my MockErrorOperation, wrapped in a GenericOperationCommand, didn't work because it dispatched its error event before listeners were attached. I could defer the result using a timer, but I don't see why I'd want to.
    • Caching. I could see myself creating a service-layer cache, and, again, see no reason to defer the results.


    Your docs explicitly state that Operations execute asynchronously, and so I wanted to hear more about your reasons not to allow synchronous operations. In my testing and caching cases above, am I trying to do something foolish?

    Again, thanks for the good work!

    Phil

  2. #2
    Join Date
    Dec 2008
    Location
    Brussels
    Posts
    407

    Default operations

    Hey Phil,

    Operations are always asynchronous because they are an abstraction for all the different async tasks that are available in the Flashplayer/Flex environment. Loading of modules, calling remote objects, etc. Therefore it doesn't make sense to also allow synchronous operations, it would defeat the initial purpose of the abstraction.
    As for your examples of synchronous operations, I would not recommend such an approach because the same class would exhibit different behaviour depending on certain circumstances. If you need to unittest a class that uses operations, in the 'real world' its execution would be asynchronous (i.e. it calls a remote object), but in your test situation it would be synchronous. In other words, you're not testing the proper situation. I'm afraid a mock operation would indeed need to simulate a remote call by using a timer.
    You can check out this class for a simple example:
    MockOperation

    As for the caching, I think you'd need to shift the caching check to a different point in your business logic. For instance, if you use a command that retrieves certain data using an IOperation you could perform the cache check inside the execution logic of the command. If there needs to be a data refresh you create the operation, if the data is still valid, just return the previously retrieved content.

    I hope I'm making sense here. If you have any more questions or comments we'd love to hear about them.

    cheers,

    Roland
    Last edited by 666shooter; Apr 4th, 2010 at 02:37 PM. Reason: fixed typo's

  3. #3
    Join Date
    Feb 2010
    Posts
    6

    Default

    Quote Originally Posted by 666shooter View Post
    Hey Phil,

    Operations are always asynchronous because they are an abstraction for all the different async tasks that are available in the Flashplayer/Flex environment. Loading of modules, calling remote objects, etc. Therefore it doesn't make sense to also allow synchronous operations, it would defeat the initial purpose of the abstraction.
    As for your examples of synchronous operations, I would not recommend such an approach because the same class would exhibit different behaviour depending on certain circumstances.
    Hi Roland.

    Thanks for your reply. It makes sense.

    I guess my underlying question was, "Does it make sense to define an abstraction that can sometimes behave synchronously and sometimes asynchronously?"

    It sounds like your answer is no...that whether something is synchronous or asynchronous is fundamental to the abstraction itself and should be called out.

    And that makes sense to me. I can see how knowing that an API is asynchronous allows me to reason better about the API and to make some simplifying assumptions (likewise with knowing that the API is synchronous).

    Phil

  4. #4
    Join Date
    Dec 2008
    Location
    Brussels
    Posts
    407

    Default yez

    Hey Phil,

    Yep, that's about the gist of it I'd say. For synchronous behaviour there's always the ICommand interface. Its execute() method returns the result of the implementation, if you use the IASyncCommand interface you'll need to hook up event listeners since it's result is retrieved asynchronously.
    Of course, in that case you can check if execute() returns null, in that case the behaviour is async, if it returns a non-null value, it was synchronous.
    That way you can create the sync/async behaviour for yourself should it makes sense for your particular situation.

    cheers,

    Roland

  5. #5
    Join Date
    Apr 2010
    Posts
    18

    Default

    I must admit I had exactly the same reaction as Phil when i discovered the operations API.
    I expected operations to work the same way with sync and async code while it was not the case.
    I wanted my service in my app to return IOperation objects and be able to listen to operation results.
    It worked until I noticed problems with loading modules.
    (ModuleManager load() methods returns (dispatches events) immediately if a module has already been loaded hence my Service operation listeners were not executed every time)

    So I had to refactor my service to return an IAsyncCommand rather than an IOperation (to have time to register listeners before the actual operation starts).

    So, my recommendation would be to use operations ONLY when you are absolutely sure the operation implementation will indeed be asynchronous. For all the other cases, I'd rather use an IAsyncCommand which looks more 'safe' to me.

  6. #6
    Join Date
    Feb 2010
    Posts
    6

    Default IAsyncCommand feels safer

    Ozeebee, I also refactored my services to use IAsyncCommands instead of IOperations because I preferred not to make assumptions about when my API's clients would register listeners.

    Did you write your own IAsyncCommand implementation to handle module loading? GenericOperationCommand wouldn't do what you'd expect - at least I don't think it would:

    Code:
    		public function execute():* {
    			_operation = ClassUtils.newInstance(_operationClass,_constructorArguments);
    			_operation.addCompleteListener(operationComplete);
    			_operation.addErrorListener(operationError);
    			if (_operation is IProgressOperation){
    				(_operation as IProgressOperation).addProgressListener(operationProgress);
    			}
    		}

  7. #7
    Join Date
    Apr 2010
    Posts
    18

    Default

    You're right, I don't trust GenericOperationCommand either

    I have actually rewritten the LoadModuleOperation class to make it implement the IAsyncCommand interface.

    It looks like this :
    Code:
    	…
    	public class LoadModuleOperation extends AbstractProgressOperation implements IAsyncCommand {
    		protected var moduleInfo:IModuleInfo;
    		
    		private var applicationDomain:ApplicationDomain;
    		private var securityDomain:SecurityDomain;
    		private var moduleFactory:IFlexModuleFactory;
    		
    		public function LoadModuleOperation(moduleURL:String, applicationDomain:ApplicationDomain=null, securityDomain:SecurityDomain=null, moduleFactory:IFlexModuleFactory=null) {
    			Assert.hasText(moduleURL, "The moduleURL argument must not be null or empty");
    			super();
    			init(moduleURL, applicationDomain, securityDomain, moduleFactory);
    		}
    		
    		protected function init(moduleURL:String, applicationDomain:ApplicationDomain, securityDomain:SecurityDomain, moduleFactory:IFlexModuleFactory):void {
    			this.applicationDomain = applicationDomain;
    			this.securityDomain = securityDomain;
    			this.moduleFactory = moduleFactory;
    			moduleInfo = ModuleManager.getModule(moduleURL);
    			moduleInfo.addEventListener(ModuleEvent.READY, readyHandler, false, 0, true);
    			moduleInfo.addEventListener(ModuleEvent.ERROR, errorHandler, false, 0, true);
    			moduleInfo.addEventListener(ModuleEvent.PROGRESS, progressHandler, false, 0, true);
    		}
    		
    		public function execute():* {
    			moduleInfo.load(applicationDomain, securityDomain, null, moduleFactory);
    		}		
    	…
    Of course, now I have to call the execute() method to actually run the operation
    But at least I'm sure the listeners are triggered whatever the behaviour of the underlying implementation.

  8. #8
    Join Date
    Dec 2008
    Location
    Brussels
    Posts
    407

    Default hmm

    Hey guys,

    I'm still a bit puzzled. I guess I'm going to have to put a bigger disclaimer on the Operations documentation page.
    If you read the introduction:
    Spring Actionscript - Operations API

    Don't these first paragraphs clearly state that operations deal with asynchronous functionality? So, when you state:
    I expected operations to work the same way with sync and async code
    to me, this doesn't make sense, because operations should not be in any synchronous code.

    What you consider a workaround (or so it kind of sounds to me here), i.e. using an IAsyncCommand is actually the right way of going about this. This is because you can add the code that checks whether the async operation is necessary in the command implementation, which would be the right separation of concerns.

    If you REALLY want to stick this in an operation's implementation then the only way to go about this would be to pass in your event handlers as constructor arguments. That way you can handle the event listening yourself in your initialisation code. But again, I recommend against this since it feels really hacky to me

    Its interesting to read your thoughts though, I will give it some thought on how to elaborate the documentation a little to prevent future confusion for any other users. So thanks for posting your comments here, its always helpful.

    cheers,

    Roland

  9. #9
    Join Date
    Apr 2010
    Posts
    18

    Default

    Hi Roland,
    I understand you opinion and I like the abstraction but in practice people will make the same mistake as we did (the LoadModuleOperation is a good example) and will have bugs to track down.
    I think the misuse comes from the fact that in the Flex platform, developers attach listeners to a component and expect them to be triggered whatever the sync/async behaviour behind.
    With the operations API, it is not the case anymore. you have to know how the implementation works to use the appropriate interface (IOperation/IAsyncCommand).

    Now, that's right, I should have read the documentation before starting coding my operation
    RTFM !

  10. #10
    Join Date
    Dec 2008
    Location
    Brussels
    Posts
    407

    Default :)

    Hey,

    I think I'll just add some clarifications to the ASDoc as well, that way it'll hopefully stick out more as well when using the classes/interfaces in someone's code.
    The case of the LoadModuleOperation is indeed a nasty one, I didn't know that the ModuleManager checks for itself if the given URL has already been loaded, and if so, immediately dispatches the event. I will add a timer to that class that executes the load() a bit later, this is a trick I've seen being used in the Flex fraemwork as well, to be able to add eventlisteners. I think it was somewhere in the ResourceManager, I'll try and look it up and post back here.

    cheers,

    Roland

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •