Passing result of one command as an input for another

pa3's Avatar

pa3

16 Nov, 2012 08:28 AM

Hi, guys!

In my application I have main configuration file (main.cfg) which holds urls of other configuration files (e.g. localization files). Main configuration is YAML file and looks like this:

localization:
  en: /some/locales/base/url/en.cfg
  ru: /some/locales/base/url/ru.cfg
  de: /some/locales/base/url/de.cfg
...
backgrounds: /some/backgrounds/base/url/backgrounds.cfg
...

Currently, I have all configurations bootstraping performed at appication startup in one single command PreloadAllConfigsCommand, which loads main config and after that it issue loading of localizations, backgrounds and all that stuff. It looks like that:

public calss PreloadAllConfigsCommand  extends Command {

  [Inject]
  public var configsRetriever:IConfigsRetriever;

  [Inject]
  public var localizationService:ILocalizationService;

  [Inject]
  public var backgroundsModel:BackgroundsModel;

  override public function execute():void {
    commandMap.detain(this);
    configsRetriever.loadConfig("some/url/main.cfg").onComplets(onMainConfigLoaded);
  }

  private function onMainConfigLoaded(config:Dictionary):void {
    var currentLocale:String = localizationService.currentLocale;
    configsRetriever.loadConfig(config["locales"][currentLocale]).onComplets(onLocalesLoadedLoaded);    
  }

  private  function onLocalesLoadedLoaded(config:Dictionary):void {
    localizationService.localizationData = config;
    configsRetriever.loadConfig(config["backgrounds"]).onComplets(onBackgroundsConfigLoaded);
  }

  private function onBackgroundsConfigLoaded(config:Dictionary):void {
    commandMap.release(this);
    backgroundsModel.doSomethigWithConfig(config);
    dispatchEvent(new ConfigsLoadedingCompletedEvent());
  }
}

So far it worked well for me, but recently I got a task to let users to switch locale in runtime. So, I have to break this command in some sub-commands, and introduce couple of macro commands - PreloadAllConfigsCommand and SwitchLocaleCommand. PreloadAllConfigsCommand will sequentially run following commands:

  • LoadMainConfigCommand
  • LoadLocalizationsConfigCommand
  • LoadBackgroundsConfigCommand
  • LoadWhaterverConfigCommand

SwitchLocaleCommand will sequentially run:

  • LoadMainConfigCommand
  • LoadLocalizationsConfigCommand

Here we are getting to the point of this question. Sequential commands execution is not a problem at all. But what is the best way to pass results of LoadMainConfigCommand as input to the LoadLocalizationsConfigCommand:

public calss LoadMainConfigCommand extends Command {
  
  [Inject]
  public var configsRetriever:IConfigsRetriever;

  override public function execute():void {
    commandMap.detain(this);
    configsRetriever.loadConfig("some/url/main.cfg").onComplets(onMainConfigLoaded);
  }

  private function onMainConfigLoaded(config:Dictionary):void {
    commandMap.release(this);
    // How should I let LoadLocalizationsConfigCommand know about parsed main config?
  }
}

public calss LoadLocalizationsConfigCommand extends Command {
  
  [Inject]
  public var configsRetriever:IConfigsRetriever;
  [Inject]
  public var localizationService:ILocalizationService;

  override public function execute():void {
    commandMap.detain(this);
    var mainConfig:Dictionary = ???; // Where should i get it from?
    var currentLocale:String = localizationService.currentLocale;
    configsRetriever.loadConfig(mainConfig["localization"][currentLocale]).onComplets(onLocalizationFileLoaded);
  }

  private function onLocalizationFileLoaded(config:Dictionary):void {
    commandMap.release(this);
    localizationService.localizationData = config;
  }
}

Right now I see couple of options:

  • Dispatch MainConfigLoadedEvent with parsed config as a payload from LoadMainConfigCommand and dynmicaly map and unmap events to appropriate commands in macro-commands PreloadAllConfigsCommand and SwitchLocaleCommand. PreloadAllConfigsCommand execute methos will look something like that:
  override public function execute():void {
    commandMap.mapEvent(MainConfigLoadedEvent.EVENT_TYPE, LoadLocalizationsConfigCommand);
    commandMap.mapEvent(MainConfigLoadedEvent.EVENT_TYPE, LoadBackgroundsConfigCommand);
    commandMap.mapEvent(MainConfigLoadedEvent.EVENT_TYPE, LoadWhaterverConfigCommand);
    commandMap.execute(LoadMainConfigCommand);
  }

And SwitchLocaleCommand execute method will be:

  override public function execute():void {
    commandMap.mapEvent(MainConfigLoadedEvent.EVENT_TYPE, LoadLocalizationsConfigCommand);
    commandMap.execute(LoadMainConfigCommand);
  }
  • Use some lib for sequencing commands (e.g. MacroBot) and store all in-between results in appropriate models, e.g. LoadMainConfigCommand will store parsed main config in MainConfigModel. So commands LoadLocalizationsConfigCommand and LoadBackgroundsConfigCommand will expect filled MainConfigModel to be injected in them, e.g. LoadLocalizationsConfigCommand will be:
public calss LoadLocalizationsConfigCommand extends Command {
  
  ...
  
  [Inject]
  public var mainConfig:MainConfigModel;

  override public function execute():void {
    commandMap.detain(this);
    var mainConfig:Dictionary = mainConfig.config;
    var currentLocale:String = localizationService.currentLocale;
    configsRetriever.loadConfig(mainConfig["localization"][currentLocale]).onComplets(onLocalizationFileLoaded);
  }
  
  ...
}
  • Pass array of events as an input to LoadMainConfigCommand and let it fill them with parsed config and dispatch when work is finished. In this scenario I'll end with HandleMainConfigEvent and its sub-classes LoadLocalesConfgEvent, LoadBackgroundsConfigEvent, etc. each mapped to appropriate command. Again, in code it will looks like:
public calss LoadMainConfigCommand extends Command {
  
  [Inject]
  public var event:LoadMainConfigEvent;

  [Inject]
  public var configsRetriever:IConfigsRetriever;

  override public function execute():void {
    commandMap.detain(this);
    configsRetriever.loadConfig("some/url/main.cfg").onComplets(onMainConfigLoaded);
  }

  private function onMainConfigLoaded(config:Dictionary):void {
    commandMap.release(this);
    for each (var e:HandleMainConfigEvent in event.eventsToHandlePayload) {
      e.config = config;
      dispatch(e);
    }
  }
}
  • Avoid resuable work to be done via commands at all and implement preloadAllConfigs and switchLocales in some Actors.

I really like the idea of using commands as resuable chunks of work, but how will I pass output from one command as an input to the next one without letting first command to decide with event to fire at the end of it's work. Or may be I am driving completely wrong direction and misunderstood the idea of commands?

  1. 1 Posted by neil on 16 Nov, 2012 10:29 AM

    neil's Avatar

    Your commands should be blissfully unaware of their sequential position. But they do need the correct input. The best way to keep everything decoupled is to manipulate external models, even if those models exist only for the duration of the process.

  2. 2 Posted by pa3 on 19 Nov, 2012 11:10 AM

    pa3's Avatar

    Really, storing in-between data in models looks like the best way to perform inter-commands communication. The drawback is that if I want to clear that data at the end of commands sequence I have to do it in some other place, not in the command which populated that models, which sounds like a responsibilities mess for me. I think, I have to introduce one more rule to our team's conventions: commands should always produce final and meaningful data (i.e. no model with unparsed/raw data).

    Thanks for you advice, Neil!

  3. 3 Posted by neil on 19 Nov, 2012 11:41 AM

    neil's Avatar

    No prob. Clear data in command at end of sequence?

  4. Ondina D.F. closed this discussion on 29 Nov, 2012 10:11 AM.

  5. pa3 re-opened this discussion on 04 Dec, 2012 09:17 AM

  6. 4 Posted by pa3 on 04 Dec, 2012 09:17 AM

    pa3's Avatar

    Exactly, what I've finally done. Thanks!

  7. pa3 closed this discussion on 04 Dec, 2012 09:18 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