Skip to end of metadata
Go to start of metadata

IoC is a modern and correct concept for structuring code and organising dependencies. As a result of the "Pupusa Conversation" of September 2008, we have in our framework a form of "semi-IoC" - this implements a (smaller) part of the affordances of IoC, being a form of declarative initialisation by name lookup. This is implemented in the framework utility initSubcomponents.

Problem

However, the facilities provided by initSubcomponents are now beginning to show some strain in the more complex components, particularly in the Pager. In a development chat of mid-May, we discussed some important ways in which the Pager could be refactored, but a lot of the infelicities in its current organisation can be traced to the lack of "strong IoC" - that is, the ability to pull dependencies from the environment rather than having to push them in initialisers.

A classic case of this can be seen in the "signature" (the term "signature" is a bit advised within Javascript but there is still a moral effect) of the family of "sort functions" configured currently by default as follows:

        sorter: fluid.pager.basicSorter,

where

    fluid.pager.basicSorter = function (overallThat, model) { 

Since "sorters" are part of a "polymorphic" family, since they are all constrained to enjoy the same instantiation signature, it simply has to be "over-broad" - since it cannot be completely predicted what component functionality a sorter might require, it has to be "lazily" passed the total top-level component as a part of overallThat. This kind of slackness makes components hard to understand, and hard to refactor - since there are no visible constraints on the dependencies a component may have.

The converse of this problem can be seen in the signature for the two pageList subcomponents:

    fluid.pager.renderedPageList = function (container, events, pagerBarOptions, options, strings) {
    fluid.pager.directPageList = function (container, events, options) {

This slightly gets away by exploiting the Javascript slackness on matching argument counts... but the load imposed on the initiating component is severe. It is clear that this attempt at dependency precision is unstable - each new dependency demanded by a potential subcomponent pushes visibility of the demand to the top-level component - which needs to be edited every time a previously unanticipated dependency appears.

The proliferation of evil can be seen from the instantiation site of the pageList subcomponent, which occurs itself at a subcomponent level within the 2nd-level component fluid.pager.pagerBar:

        that.pageList = fluid.initSubcomponent(that, "pageList", 
           [container, events, that.options, fluid.COMPONENT_OPTIONS, strings]);

Not only is pagerBar painfully aware of all the details of this instantiation list, it has also had the presence of strings pushed into its own argument list, propagating and multiplying the disorder. There can be seen to be a half-hearted attempt to impose this 5-argument pattern as a kind of "standard" for subcomponents at this level. This is what IoC is for:

Proposal

The proposal to resolve these kinds of problems through the use of "strong IoC" looks as follows. Analogous to our existing configuration registrar in the form of fluid.defaults, which takes care of concrete, top-level defaults to components, we propose a parallel scheme named fluid.demands, or perhaps fluid.wiring, or perhaps some other name.

Unlike fluid.defaults which is configured relative to a single component, fluid.demands configuration is relative to a particular pairwise relation. The signature looks something like this:

fluid.demands(contextComponentName, demandingComponentName, [ wiring spec ]);

In the context of the above pager examples, for example, contextComponentName could be "fluid.pager", demandingComponentName as "fluid.pager.renderedPageList". So - the first argument establishes a "context name" - an overall component during the instantation of which the particular dependency set will be demanded. The second argument determines the name of the subcomponent which is demanding the particular dependencies in that context. The wiring spec, takes the form of a list of EL expressions [and possibly some other things?], like so:

fluid.demands("fluid.pager", "fluid.pager.renderedPageList", 
       ["that.container", "that.events", "that.pagerBar.options", fluid.COMPONENT_OPTIONS, "that.options.strings");

Here, all the EL paths are referred to top-level - any beginning with that will be referred to the component which forms the context name during instantiation, although we may imagine specific qualification perhaps of the form that{fluid.pagerBar}.options perhaps to designate specific that types in the current initialisation stack. The use of fluid.COMPONENT_OPTIONS has its usual meaning of designating the placeholder for literal arguments targetted at this component in the overall options.

With this syntax, specific arguments, or even the invocation, of fluid.initSubcomponent would go away. In terms of our overall Component Lifecycle as referenced at the beginning of the page, we would place a general directive like fluid.initDependents or so at the end of the constructor function. The current "type class argument" to fluid.initSubcomponent also needs to go somewhere - but instead of putting it somewhere, we observe the following:

This was also part of a general discussion with respect to graceful degradation - it would be useful to create "giant switchboxes" of components, which as well as allowing dynamic wiring of structure, could also allow conditional construction - based on presence of failure of conditions (expressed as nullary functions), component implementations could be conditionally suppressed or swapped out for others. The concept was of a top-level initialiser,

fluid.instantiate(container, { options } );

which simply took an "IoC block" of options and guaranteed to instantiate "anything". In order to preserve idiomaticity for users of the framework, we continue to allow direct invocation of constructor functions as an alternative style. All of the functionality of fluid.instantiate however would remain available for subcomponents - we would have a "universal stickle-brick area" sitting in the options space for every component participating in fluid.initDependents:

fluid.pager.pagerBar(container, {
  selectors: {
     blah blah 
     }
  components: {
    pageList: {
      type: "fluid.pager.renderedPageList",
      options: {
         stuff
         }
    }

Any and all components designated within the "universal area" would be instantiated by the operation of the "terminal directive" initDependents and attached to the that as if the old-style initSubcomponents had been invoked on them manually, and any wiring designation determined by fluid.demands would be used to discover dependency demands.

Thus the feet of humanity were set irrevocably upon the right path6.

  • No labels