The Curious Case of the Justifiable but Slow Singleton

| 3 Comments

One of the most cogent criticisms of the book Design Patterns is that too many people read it as "here's a list of characteristics your software should exhibit" instead of "here's a catalog of common design elements many projects exhibit". Rather than spreading a common vocabulary, the patterns book became yet another set of buzzwords to use to spice up developer CVs.

The backlash should have been predictable; it's almost a law of physics by this point. For every grand unifying pronouncement about a shiny new way to improve software development, cue a grand group of people ready to disclaim it as unnecessary, overcomplicated, a warmed-over rediscovery of something at least thirty years old, or a silly way to sell books and consulting. In this case, the naysayers had a point. Suddenly, global variables weren't bad. They were instances of the Singleton pattern. Oh frabjous day.

(Fortunately in these enlightened times, we use inversion of control and dependency injection to make our singletons, and we control them with great swaths of barely-typed XML or JSON. We're so modern it hurts.)

... not that singletons are always bad.

Some concerns really are global. Your logging framework is probably a global concern. Your configuration file is probably a global concern. Here's a secret, though: these things probably oughtn't be mutable. The real concern is mutable global data. (A secondary concern is too much coupling to concrete instances, but that's a different article.)

Sometimes you can go a little too far the other direction, though.

I have a test suite that's too slow for my taste—about 2500 assertions which run in 70 seconds. I'd love to get that down to 20 seconds, but under a minute is definitely an improvement.

The project has a configuration file in config/settings.yaml and a corresponding Project::Config module which loads the settings and offers an API to its contents. Other modules within the system access the configuration file by loading the module and calling methods on it.

Because this configuration information is global to a process, the configuration module stores the data structure containing the configuration in a lexical variable global to the module:

package Project::Config;

my $config;

sub load_config
{
    $config = load_config( 'config/settings.yaml' );
}

1;

Everything was well and good until I saw YAML taking up more than 10% of the execution time of one of the test files. I traced it to the configuration module... which loaded the configuration file anew from its import() method.

For every other module in the system which used Project::Config, it dutifully re-read the configuration off of the disk.

The tests run a little faster now.

This was a silly little pessimization anyone could have made, but it illustrates two interesting points. First, it shows that whoever wrote this code (I don't know who and I didn't look, because it could have been anyone) clearly had singleton concerns in mind, and rightly so. This is process-global data and it deserves to be available everywhere. Sure, the loading was a pessimization, but that's fixed now and everything still works. Success.

I take more interest in a subtler question: how should other modules within the system access this configuration data? The current access pattern is use Project::Config and call methods that way, but that demonstrates the concrete coupling problem I alluded to earlier, and it certainly exacerbated the multiple-loading problem I fixed. What if, instead, something external to the system could somehow inject an already-instantiated configuration object into the other entities, such that none of them had to couple themselves to the concrete module-name-to-filepath-mapping that eventually called Project::Name's import() and reloaded the configuration file?

Yes, that probably would have hidden the pessimization from my traces, but would that have mattered? It would also have hidden the effects of that pessimization.

That's not the only goal of my development process, but it's a benefit, and that's something to consider.

3 Comments

Just one note - that injected configuration does not need to be an object. It can be just a scalar or two or even a hash. That does not matter - the point is that it is the module you use that should define it's configuration (and other parameters). This way you can reuse your modules in other projects without carrying with you the whole configuration management - or even you can publish it to CPAN!

chromatic, thanks for looking at this problem space. It seems like it would particularly easy to make details form a config file available to an application, but it's surprisingly hard to do it well-- making sure the config file is read a minimum number of times, and also allowing project variations that might override some values.

I was looking at this problem space last week. In particular I was looking at possible replacements for CGI::Application::Plugin::Config::Perl based on a Moose Role and a Singleton. Even with that narrow focus, it's surprising how many attempts are out there to pre-package a solution. Here's a sampling of what I found. There are many more projects on CPAN that include their own home-grown config file solutions:

* Config::Role -- Uses File::HomeDir and Config::Any.
* MooseX::SimpleConfig -- requires the configuration to be in YAML.
* MooseX::Configuration -- requires an INI-style config file.
* MooseX::ConfigFromFile -- An abstract class. A sub-class could read our Perl-based config file and return an object configured with.
* MooseX::App::Plugin::Config -- It's part of the MooseX::App framework, and not a standalone thing. It could read a Perl-based config file, using Config::Any among other things.
* MooseX::Singleton -- Helps with singleton storage generally
* Mojito::Role::Config -- a project-specific example
* Config::Tiny::Singleton -- uses INI-style config files

Here's what I keep with myself for a project:

https://gist.github.com/4088910

Feedback welcome. I don't consider the design "done" yet. Eventually I want to support overriding some values in the configuration file, and end up with multiple singletons.

For example, mostly I want to deal with same configuration details throughout the code for a large website. But on the related mobile site, a few details change, but most are the same.

Deep recursion on subroutine "Project::Config::load_config"

Modern Perl: The Book

cover image for Modern Perl: the book

The best Perl Programmers read Modern Perl: The Book.

sponsored by the How to Make a Smoothie guide

Categories

Pages

About this Entry

This page contains a single entry by chromatic published on November 15, 2012 5:13 PM.

API as Documentation was the previous entry in this blog.

How Bugs Get Fixed is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.


Powered by the Perl programming language

what is programming?