Dallaylaen has asked for the wisdom of the Perl Monks concerning the following question:

Where to put initialization code (connecting to databases, generating stuff, loading data files, etc)?

Here are some existing options:

  1. Put it into the module in question. This will execute the code in BEGIN{} when the module is used.

    (+) Good encapsulation.

    (-) Everything is tied to databases, configuration, specific file locations etc. Hard to isolate unit tests, even harder to run code snippets to find bugs, as in perl -MFoo -d -we 'Foo->new'

  2. Put it into separate script (startup.pl etc)

    (-) Code away from where it's used, easy to forget something.

    (-) Initializing all-or-nothing.

  3. startup() (or other name) routine in every module in project.

    (-) still easy to forget to run it.

  4. Use Perl's built in INIT{} block. Here's the problem (same for Apache, of course):
    bash$ plackup -e 'use warnings; INIT{ warn "foo"; }; sub { warn "here +"; return [200, [], []] };' Too late to run INIT block at (eval 7) line 1. HTTP::Server::PSGI: Accepting connections at http://0:5000/ here at (eval 7) line 1. 127.0.0.1 - - [20/Nov/2014:14:18:08 +0200] "GET / HTTP/1.1" 200 - "-" + "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:33.0) Gecko/20100101 Firef +ox/33.0"

    Note the Too late to call INIT warning and no sign of "foo" in output.

Now I'd like to have a module which is used as follows:

In project's module:

use Init::Queue sub { get_dbh(); load_file(); build_cache(); }; # postpone till explicitly called

In production/initialization code:

Init::Queue->startup(); # this executes all startup blocks, # in order of appearance

Is there such a module? If not, is it needed? Or is there a simpler approach which I've overlooked?

Crosspost: stackoverflow

Replies are listed 'Best First'.
Re: Module for executing postponed initialization code
by Corion (Patriarch) on Nov 20, 2014 at 12:57 UTC

    I use two approaches for this.

    Either, a module documents and exports an explicit initialization routine:

    package Foo; my $dbh; sub init_foo { my( %script_config )= @_; # Read config file ... # Connect to database $dbh ||= $config{ dbh } || connect_to_foo_database( %script_config + ); # Set up cache ... };

    Alternatively, I construct these things when they are first needed:

    sub frobnicate { my( $self, %options )= @_; $options{ dbh } ||= $self->get_dbh; ... }; sub get_dbh { my( $self )= @_; $self->{ dbh } ||= do { DBI->connect( $self->{dbi_parameters} ); }; };

    The approach is more explicit in the sense that things fail early, but it needs the explicit call by the person writing the script.

    The other approach is more implicit in the sense that things only get initialized once they are needed.

    I don't like the implicit magic of having a special name for the module which is called to configure things.

    If I had to implement such magic, I would have something like this:

    package Module::MagicInit; use strict; my @init_queue; sub register_init { push @init_queue, [ (caller(1))[2], @_ ]; }; sub init { for my $task (@init_queue) { my( $package, $callback, @args )= @$task; $callback->( @args ) or die "Init failed for $package."; }; };

    ... and then use that like so:

    package Foo; use Module::MagicInit; register_init( \&my_init, 'Hello', 'World' ); sub my_init { my( $greeting, $name )= @_; print "$greeting, $name\n"; }; 1;
    #!perl -w use strict; use Module::MagicInit; ... Module::MagicInit::init();

      Thanks for your reply.

      Yes, the possible "magic" implementation I had in my head is close to yours.

      As for the implicit approach, I really like this one, but there's a caveat. I would like the init code to fail early and loudly so that deficient and/or misconfigured installation does not go into production.

Re: Module for executing postponed initialization code (Devel::Init)
by tye (Sage) on Nov 20, 2014 at 14:42 UTC

    Devel::Init

    Update: For those who (understandably) don't realize that there really is pertinent information for this thread available if you follow the above link (slightly paraphrased):

    The documentation lays out best practices for ensuring that initialization will always be done before it is needed and not before it is possible, even when complex interdependencies between initialization steps exist.

    Devel::Init allows module authors to declare what initialization steps are required and then allows each situation to very easily drive which types of initializations should be run when (and which types should not be run until they are actually needed).

    And Devel::Init makes it easy to write your initialization code so that a circular dependency will be detected and be reported in an informative way.

    - tye