in reply to Best way to 'add modules' to web app?

One useful extension technique for a server-side application like this is to support a "plugins" directory in to which users can place Perl modules that will be automatically loaded when your application is initializing itself. Then you can document some key functions within your code, and show people how to override them in their plugin.

Here's an untested schematic example:

# In myscript.cgi, or your mod_perl configuration, or wherever: # This could be relative to the script path or config file my $plugin_dir = '/opt/myscript/plugins'; sub load_plugins { # Read in the names of all .pm or .pl files in the plugins dir. # (Even better would be a recursive search of subdirectories.) unless ( opendir(PLUGINDIR, $plugin_dir) { warn "Plugin dir '$plugin_dir' is missing or unreadable."; return; } my @filenames = grep /\.p[ml]/, readdir(PLUGINDIR); closedir(PLUGINDIR); # Put the plugins directory in our library search path. # This allows one plugin to use or require another one. local @INC = ( $plugin_dir, @INC ); # Try to load all of the plugin files, but don't die trying. foreach my $plugin ( sort @filenames ) { eval "require $plugin"; if ( $@ ) { warn "Skipping plugin $plugin: $@" } } } sub print_output { print @_; } load_plugins(); print_output( "<html>Here's my CGI script output.</html>" );

Then, as a user, if I install this script, and read your documentation, I can "add on" some functionality by writing my own code into a file in the plugin_dir that modifies your original functions. For example:

# In $plugins_dir/uppercase.pm: sub print_output { print map { uc } @_; }

A key benefit of this approach is that when the original author releases an updated version of the main program, users can install it without worrying about overwriting their local customizations. Of course, if you make big changes to the main script, the plugins may not work properly anymore, so you need to either promise to maintain a backwards-compatible API, or check version numbers or such.

Following standard techniques of good program design when writing your application should generally also make it easier for your application to be extended this way. For example, finding common sequences of operations and breaking them out into subroutines with clear, descriptive names helps construct a clear API that someone else can later come along and extend.

If you have multiple plugins that all want to be involved in the same process, they'll need to wrap around each other or use some kind of event-handler logic to let them cooperate.

Here's a brute-force wrapping technique:

# In $plugins_dir/uppercase.pm: my $inner_print_output; BEGIN { $inner_print_output = \&print_output } sub print_output { &$inner_print_output( map { uc } @_ ); }
# In $plugins_dir/remove_whitespace.pm: my $inner_print_output; BEGIN { $inner_print_output = \&print_output } sub print_output { &$inner_print_output( map { s/\s//g } @_ ); }

Because of the "sort @filenames" expression in load_plugins, these files will be loaded in alphabetical order; first remove_whitespace.pm wraps around the original print_output, then uppercase.pm wraps around that. The result is that uppercase.pm's print_output calls remove_whitespace.pm print_output which calls our original print_output. Feel free to use some other sort order, but make sure it's consistent so that users can control the order they want things loaded in.

Note that the core concept described here is orthogonal to whether you're using functions or objects; if your web application is implemented using objects and factories, as described in other responses to this question, the "plugins" modules can define new classes and register them as needed.