Thursday, May 29, 2008

Modularity & Hygiene II

A similar, but distinct situation involves the environment expected by a module when it is activiated:

  • What needs to be set up for the called function
  • What is expected to be unperturbed from a “pristine” environment -- and the definition of that pristine environment.

This places restrictions upon use e.g., the inability to initiate workflows within a page flow for seam.

These restrictions result from an implicit dependency upon the configuration of the calling environment. Since the appropriate configuration is assured if the module was called as planned, there is little checking to verify that the environmental assumptions have been met. Once the nested calling paradigm has been adopted, design choices are biased towards implicit configurations that cannot be easily set without perturbing the calling environment, since B (and A for that matter) can still reference the external environment.

I’ve found a useful way to think of this as being the difference between a linear and a nested calling environment: a “linear environment” would pass parameters in as a single object which contains required values while the nested approach sets up one or more global variables for access by the called functionality.



In the “linear” illustration nothing in the external environment is perturbed, side effects are minimal and any arguments could be copied, modified and passed onto the next module in a sanitary fashion (with the usual caveats around shared “stream-like” objects).

Admittedly there are some times when the nested approach makes the most sense, usually for “stream like” variables, e.g. an initialization step to read in a configuration file, initialize connections etc.. The problem arises when there is no way to spawn a new configuration (or initialize one if you’re called in a different context). In lieu of such an idealized situation, it would be useful, at a minimum, to be able to detect that you’re being called in the wrong context. When it is difficult for a module to decide if is being called in the correct context (or if it elides the context check), it is hard, if not impossible to provide easy to use modules.

Note: I think that the rails trick of overloading the const-missing exception handler is arguably in this space. The magic underlying this functionality wasn’t easy (for me) to find and it surfaced the capability in such a way that it couldn’t be used for my purposes, since it had no introspection capability and only covered the case of exceptions generated during system operation. Note: discovering this also helped answer one aspect of the environment that I had previously found opaque: “why does my new code seem to be loaded in some cases but not in others.” The answer being that the new code (class definitions etc.) was retrieved if the class had not been previously loaded. If it had been previously loaded the const-missing exception would not be generated and the class would not be reloaded.

At some level I have no problem with this implicit ‘environment’ structuring as long as there are ways to determine what environment you’re in and have the ability to spin up a new environment appropriately configured as necessary.

It’s also important to distinguish environment set-up and configuration using well defined files/apis and this is a perfectly reasonable and necessary practice to set up such things as database access, service locations etc in configuration files that are managed by an “environmental processor”

They are usually
  • One shots done at startup, or in response to a specific reconfiguration request.
  • accessed through a well defined API
  • ‘stream-like’ in nature

Again these are issues that I’m experiencing as I spin up my application. I’m not claiming that their solution is either easy or practical, only that it is desirable.

No comments: