Passing result of one command as an input for another
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?
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
1 Posted by neil on 16 Nov, 2012 10:29 AM
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 Posted by pa3 on 19 Nov, 2012 11:10 AM
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 Posted by neil on 19 Nov, 2012 11:41 AM
No prob. Clear data in command at end of sequence?
Ondina D.F. closed this discussion on 29 Nov, 2012 10:11 AM.
pa3 re-opened this discussion on 04 Dec, 2012 09:17 AM
4 Posted by pa3 on 04 Dec, 2012 09:17 AM
Exactly, what I've finally done. Thanks!
pa3 closed this discussion on 04 Dec, 2012 09:18 AM.