Feature composition by bootstrap Command

Stray's Avatar

Stray

17 Jan, 2011 04:21 PM

My project is modular. There's a main application - an e-learning environment - which varies according to which mix of modules each user has.

There are also a couple of toolsets - again modular - which the managers and administrators use to change stuff and view reports etc.

In the admin tool we added a feature which lets the administrator launch the personal-learning report for any user.

The feature has been well received and they've decided that they want any user who 'manages' other users to be able to use this within the main application - to launch reports for the people they manage as well as themselves. These user/manager relationships are already part of the system, so bringing in a list of managed users to populate this stuff isn't a problem.

In fishing through the admin module to work out what I need to bring over into the main application menu module (where this will live) I've realised that I can extract the required robotlegs wirings into a single bootstrap Command, and then just include that in the main menu module.

Normally I bootstrap by 'type' - I'll have bootstraps for Commands, Services, Models and so on. But I realised that it must be possible to bootstrap by 'feature'.

This 'managed users reports' bootstrap now contains:

  • A mediator mapping for the view/mediator that lets them select a user and launch the report
  • A command mapping for the command that picks up the event dispatched by this mediator and makes the url request
  • A singleton service mapping for the factory that creates the list of user VOs from the xml

To include the feature in the main menu module I now just have to include this bootstrap command and hook up to use the factory for this data and add the correct view.

It really brings home the value of coding to interface contracts as well - if I want to vary the factory then I just switch in a different concrete implementation.

Is anyone else using bootstrap-by-feature? I'm thinking that I might play with this being my default approach to bootstrapping. Are there any gotchas?

  1. Support Staff 1 Posted by Till Schneidere... on 17 Jan, 2011 04:40 PM

    Till Schneidereit's Avatar

    Thanks for the interesting discussion of bootstrapping, Stray!

    This meshes nicely with the ideas that were brought up in
    http://knowledge.robotlegs.org/discussions/suggestions/36-postmortem-mvcs-folder-structure-sucks-suggestion-use-a-modular-folder-structure-tied-to-your-project

    The more I ponder these structuring-issues, the more structuring by
    type instead of feature reminds me of using Hungarian Notation as a
    variable naming convention. Including the bad taste that leaves in my
    mouth ...

  2. 2 Posted by squeedee on 17 Jan, 2011 05:45 PM

    squeedee's Avatar

    I love by-module-by-type-by-feature! So Nyer... Or to put it another way:

    1. My modules are my major features, as fine grained as they need to be architecturally. I structure folders by module, mostly flat in the modules dir. Components are also treated similarily (they only differ by not using RL child contexts)
    2. My types (Views/Services/Controller/Model) are best layed out by type, not feature. Within each type we'll then 'heuristically' decide to namespace based on feature. These 'features' can be grouped differently for each type. eg module.view.userForm. and module.view.userList. will both consume module.model.user.*

    Meanwhile, as for bootstrapping, for the same reason as my preference for module/type/feature (in that order), I bootstrap much the same way. Here comes the freakshow, I mean code:

    public class ConfigurationSequence extends AbstractSequencer {
        // One of the few places I use //'s over SRP.
        // Perhaps ISequencer should have a placeholder addGroup?
    
        override protected function configure():void {
            // Modelling
            addStep(ConfigureWorkersModelCommand);
            addStep(ConfigureScalesModelCommand);
            addStep(ConfigureTimelineDivisionModelCommand);
            addStep(ConfigureModuleStateCommand);
            addStep(ConfigureJobVOModelCommand);
            addStep(ConfigureAssignmentVOCommand);
            addStep(ConfigureWorkerJobModelCommand);
            addStep(ConfigureJobsModelCommand);
            addStep(ConfigureJobSelectionModelCommand);
            addStep(ConfigureJobDraggingModel);
            addStep(ConfigureWindowModelCommand);
            addStep(ConfigureJobsBusyModelCommand);
            addStep(ConfigureJobVOProxyCommand);
    
            // Services
            addStep(ConfigureJobServicesCommand);
    
            // Commands
            addStep(ConfigureCommands);
            addStep(MapIncomingCommands);
            addStep(MapOutgoingCommands);
    
            // Views
            addStep(ConfigureScrollpadsCommand);
            addStep(ConfigureDatePickViewCommand);
            addStep(ConfigureScheduleGridViewCommand);
            addStep(ConfigureScheduleHeaderViewCommand);
            addStep(ConfigureScheduleToolViewCommand);
            addStep(ConfigureTimeIndicatorViewCommand);
            addStep(ConfigureContextViewCommand);
        }
    
    }
    

    That is the bootstrap sequence code for a single module.

    Each step added, bootstraps a "by-type-by-feature". Of which, there are many :P
    If you wanted to 'pull out a feature' then I'd imagine it'd either be a candidate for 'Extract Module/Component' or simpler, something that just needs five minutes of bootstrap cherry picking.

  3. 3 Posted by Stray on 17 Jan, 2011 06:18 PM

    Stray's Avatar

    Nice!

    While we're sharing freakshows... I mean code... in the small strategy game I just built the main context has:

    // Map some Commands to Events
    commandMap.mapEvent(ContextEvent.STARTUP_COMPLETE, BootstrapGameStartup, ContextEvent, true);
    commandMap.mapEvent(ContextEvent.STARTUP_COMPLETE, StartGameCommand, ContextEvent, true);

    And then BootstrapGameStartup does...

    commandMap.mapEvent(GameEvent.GAME_STARTED, BootstrapModels, GameEvent, true);
    commandMap.mapEvent(GameEvent.GAME_STARTED, BootstrapViewMediators, GameEvent, true);
    commandMap.mapEvent(GameEvent.GAME_STARTED, BootstrapDayCycleCommands, GameEvent, true);
    commandMap.mapEvent(GameEvent.GAME_STARTED, BootstrapGameEndingsCommand, GameEvent, true);
    commandMap.mapEvent(GameEvent.GAME_STARTED, StartViewCommand, GameEvent, true);
    commandMap.mapEvent(GameEvent.GAME_STARTED, ConfigureModelsCommand, GameEvent, true);
    commandMap.mapEvent(GameEvent.GAME_STARTED, ProcessDayStartCommand, GameEvent, true);

    And then the various Bootstraps in there each do their thang.

    But boy, I can't believe I missed the need for a sugar method!

    addStep(CommandToBeBootstrapped)

    FTW!

    I shall be stealing that in approximately 2 minutes.

    Thank god this board isn't troll-tastic, or we'd be inundated with statements about how much "more code" this kind of solution is.

  4. 4 Posted by squeedee on 17 Jan, 2011 06:52 PM

    squeedee's Avatar
  5. 5 Posted by Stray on 17 Jan, 2011 07:07 PM

    Stray's Avatar

    Thanks - that's nice!

    I was even thinking of just adding an addStep(... ) sugar function to my bootstrapper to take care of mapping against the STARTUP_COMPLETE event or whatever - but your abstract sequencer is even nicer.

    Cheers!

  6. 6 Posted by Weyert on 17 Jan, 2011 11:26 PM

    Weyert's Avatar

    You could even make distinction between mandatory and optional features where failing mandatory features will stop the application running. While failing optional features silently get ignored by the application. This way you can have a feature which loads configuration files optionally and a mandatory feature which deals with asset management.

    Even you could then add some sugar and define at which moment the feature needs to be prepared. For example, during the preloading part or when during application launch time.

    A simple feature could be:

    public class InternationalisationFeature extends FeatureCommand {
    
        public function execute(): void {
             injector.mapSingletonOf(II18nService, YAMLI18NService );
             nextFeature();
       }
    }
    

    The features could then be configured in the application context or something:

    public function configureFeatures(): void {
          addFeature( InternationalisationFeature, Feature.PREPARE );
    }
    

    Not sure yet, what the best way is to deal with services because in this cause you would like to continue to the next step when the service is loaded and the model is ready prepared. I guess you can map it as a singleton after you manually instantiated it via injector. Something like:

    public function execute(): void {
       var service: InternationalisationFileService = new InternationalisationFileService();
       eventDispatcher.addEventListener( InternationalisatonEvent.LOADED, onServiceLoaded );
       service.load( 'de.yaml' );
    }
    private function onServiceLoaded(event: Event): void {
        super.nextFeature(); // like sequencer.step();
    }
    
  7. Stray closed this discussion on 13 Feb, 2011 04:46 PM.

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