To be (a role) or not to be (a role) [*]

Table of contents

To be (a role) or not to be (a role) [*]
Local::(Config, Connector, Logger, Session, Novels)
Do We Make Something a Role or a Class?
Classes 'v' Roles
Inherit from What::Exactly?
Moo and Moos in Practice
Terminology: Instantiated and Initialized
A Moo Trap for the Unwary
Moos and Role::Tiny
Moos and Moos-Role (sic)
Other Matters
The trigger feature
namespace::autoclean
Overriding Some Config Values
Winding Up and Winding Down

To be (a role) or not to be (a role) [*]

[*] Forked from the Shakespearian source code.

I'm going to add to an issue which was raised recently by chromatic: managing singletons, such as used for config files.

Among the comments to that post is Mark Stosberg's investigation into various config-file-oriented modules.

My coments will seem like I'm getting the concepts of class and role mixed up. But not really. What I'm trying to do is think of these issues from the point of view of a beginner, and to grab at solutions which such a beginner might be tempted to grab at. So this is about how to analyze issues, rather than arriving at the perfect solution by the shortest possible route.

Local::(Config, Connector, Logger, Session, Novels)

Assume I have this set of modules as the basis of this article.

In reality, the splitting out of Local::Logger and Local::Session is not a good idea. An app should have a database class (a Model, as in MVC) which manages them, and all other table-specific classes.

The point here is to provide a plausible set of modules which I want to chain together, using either just classes, or some combination of classes and roles.

o Local::Config wraps Config::Tiny

Its full purpose is spelled out in detail below, but obviously it handles config files.

o Local::Connector wraps DBIx::Connector

It tailors a database handle (dbh) according to a given config file.

o Local::Logger wraps Log::Handler::Output::DBI

This logs to a database, which database being based on which connector, i.e. which config file.

o Local::Session wraps Data::Session

This manages a CGI-style session, depending on the connector, i.e. the config file.

o Local::Novels

This is the basis of a CGI (ok, Plack/Starman) script, together with some command line scripts, which manage my database of novel, and other book, reviews. See http://savage.net.au/Novels-etc.html.

Where the following discussion talks about the App class, here it would be Local::Novels, or any similar class.

Some questions I'll try to answer:

o Do I actually need this particular set of modules?
o How do I tie these modules together?
o Which is the best class-building class to adopt?

For some definition of best, of course.

BTW: Local::* is the recommended prefix for modules which will not be uploaded to CPAN.

Do We Make Something a Role or a Class?

Firstly some graphs, and afterwards the discussion.

These SVG images have been produced with my Graph::Easy::Marpa. The input files (*.ge) can be downloaded from http://savage.net.au/Module-reviews/pod/Moo.v.Moos.tgz.

As an aside, I'll whet your appetite for the wonder of Graphviz with Moo.v.Moos.1.ge:

        # perl ~/perl.modules/Graph-Easy-Marpa/scripts/gem.pl -i Moo.v.Moos.1.ge -o $DR/Moo.v.Moos.1.svg
        # Class and label attributes have to be repeated due to what's presumably a bug in Graph::Easy::Marpa.

        graph {label: Figure 1. Classes are green and Roles are blue.\nNote: This design is faulty}

        node       {color: red}
        node.class {color: green; fontcolor: white; style: filled}
        node.role  {color: blue;  fontcolor: white; style: filled}

        [Config] {class: role; label: Config_Role} -> [Connector] {class: role; label: Connector_Role}

        [Connector] {class: role; label: Connector_Role} -> [Logger]   {class: role; label: Logger_Role}
        [Connector] {class: role; label: Connector_Role} -> [Database] {class: role; label: Database_Role}
        [Connector] {class: role; label: Connector_Role} -> [Session]  {class: role; label: Session_Role}

        [Logger]   {class: role; label: Logger_Role}   -> [App] {class: class; label: App_class}
        [Database] {class: role; label: Database_Role} -> [App] {class: class; label: App_class}
        [Session]  {class: role; label: Session_Role}  -> [App] {class: class; label: App_class}

This design tries to have a many roles as possible. The problem is that methods declared in Config and Connector are composed 3 times into App.

Use aliases (if implemented) you think? I disagree. To me that would be covering up a fault in the design.

Here, Logger and Session have been made into classes, as one way to solve the problem mentioned above.

But now we have another problem. What justifies having 2 db-oriented classes not managed by the Database role (or Database class for that matter)? Nothing that I can see.

One more try, where we make things as simple as possible...

Here, Logger and Session are no longer stand-alone classes, but are subsumed under the Database role (or class).

In other words, Database now hasa Logger and hasa Session.

More questions:

o How do we know we've got a good mix of classes and roles?
o Can we justify the existence of these classes and roles?

Or: Is this the best we can do?

Classes 'v' Roles

So how exactly does this matter? Well, this article is about deciding to make something a role or a class, which can be a complex issue.

Let's start with Local::Config, which has its own config file, '.htlocal.config.conf', part of which is:

        Local::Application = .htlocal.application.conf
        Local::Books = .htlocal.books.conf
        Local::Config = .htlocal.config.conf
        Local::DogBreeds = .htlocal.dog.breeds.conf
        Local::Flowers = .htlocal.flowers.conf
        Local::Menus = .htlocal.menus.conf
        Local::Modules = .htlocal.modules.conf
        Local::Novels = .htlocal.novels.conf
        Local::Website = .htlocal.website.conf
        Local::Wines = .htlocal.wines.conf

Besides its own config file, Local::Config can load() the config file of the other modules mentioned, using File::HomeDir to find out where they were installed.

Further, that code is exactly what I want to use if I run a program to install another module's config file. And this installation step I build into Build.PL and Makefile.PL, when I make them run scripts/copy.config.pl.

Adding to the complexity is that Local::Config is what I use to load() yet another config file, stored alongside '.htlocal.config.conf', but used by DBIx::Admin::DSNManager. It's called '.htlocal.dsn.ini', and has one section per database, like so:

        ...
        [Local::Novels]
        dsn = dbi:Pg:dbname=novels
        username = local
        password = local
        active = 1
        use_for_testing = 0
        ...

To some extent, of course, this duplicates part of '.htlocal.novels.conf'. Hence this particular use of Local::Config is not major importance.

Now, do I make Local::Config a class or a role?

Making it a class means I can use it in a program, whereas making it a role means I need another class to compose (incorporate) it into.

But wait, there's more...

I want to use Local::Config (i.e. as a class or role) inside Local::Connector, which is a wrapper around DBIx::Connector.

That way, each Local::Connector object would use a different config file to create a dbh (database handle) tailored to a different database. (Some of the above modules have their own databases, and some don't.)

Using Local::Config as a class or role would not really matter in achieving that goal.

But ... next I had the idea to use Local::Connector inside Local::Logger, which is a wrapper around something which logs to a db. As mentioned, I use Log::Handler::Output::DBI for this.

Likewise, I also want to use Local::Connector inside Local::Session, which is a wrapper around Data::Session, tailored to a specific db.

I do understand some people insist on using a different dbh for logging 'v' for general SQL ops, but for my personal needs, I don't (need to) go that far.

Now that I have Local::Logger and Local::Session, I'd plug those into Local::Modules (which manages the db of modules written or maintained by me), or into yet other modules (i.e. apps), such as Local::Novels, etc.

Not only that, but Local::Modules is used in Local::Application, which is a CGI app giving me access to module stuff (build, run git, ..., and various backup commands, etc).

By now the plan to chain these modules should be clear. The question is: What helper do I need? Moos, Moo, Moo::Role, Role::Tiny, Something::Else?

By focussing on this, and the questions raised at the ends of previous sections, I hope to choose one or more of these to base my work on.

Inherit from What::Exactly?

Now, I've been using Hash::FieldHash as my class-builder class for several years, but I wanted a few more features, so I've been assessing the alternatives.

This article is not primarily about class-builders, but rather how they impinge on the issues raised, and how their capabilites influence the decisions drawn.

Firstly, I wanted simplicity and speed. As for pure-Perl, it normally doesn't concern me, but in this case I included it in the selection criteria. Also, I wanted a Moose-like syntax, since I'm doing this partially as a test run for changing the underlying class-builder - Hash::FieldHash - used in CGI::Snapp, my fork of CGI::Application, and using a Moose-like syntax matters to me in designing the upcoming CGI::Snapp V 2.00.

Yet another question: Should speed be an important criterion, or just another box to be ticked? And, no, speed is definitely not automatically a priority.

Which means that what matters to me, above all, is reliability. Or: I never want the wrong answer at twice the speed of light. I want reliability, first, middle, and last.

Anyway, my policy on published modules at least (e.g. CGI::Snapp) is that they should not force the end-user to load Moose, even though it is the gold standard for its list of features provided.

So, I've limited real, live, code-patching to Moo and Moos.

And why those 2 anyway? Well, consider this list of class-builders I prepared recently for the Google Code-in. Such a list is both understandable and (flamebait warning!) madness.

Understandable because Perl does not (yet) ship with a definitive class-building package to solve this issue, and madness because Perl programmers, just like all other programmers, don't trust other programmers, and so are driven inexorably to reinvent the wheel at any cost.

And the true victims are the beginners, who - when confronted by this plethora of alternatives - must necessarily be very confused indeed. There is already a very nice set of Perl module-comparison reviews etc, and having the Google Code-in lead to one on class-builders, would have been an important and necessary addition, but it was not to be.

Moo and Moos in Practice

What I had wanted to do was use Local::Config as a stand-alone class and as a role. How to do this, given that these goals aren't necessarily compatible? To be specific, it depends on which class-builder you use.

Terminology: Instantiated and Initialized

Sometimes the docs (Moo) refer to an object having been instantiated, e.g. before the call BUILD(), if any. And I admit I'm keen on having BUILD(), or any equivalent, available.

This means the object has been created, and can be used to make method calls. So I can do:

        sub BUILD()
        {
                my($self) = @_;

                $self -> config_name('.htlocal.config.conf');
                ...
        }

But elsewhere, the docs (Moo - you'll have to scroll down to default) say the default values of the attributes have not necessarily been initialized during a call to new(), i.e. during a call to BUILD(). This means you can't (reliably) do:

        use Moo;
        or
        use Moos;
        ...
        sub BUILD()
        {
                my($self) = @_;

                say $self -> config_name;
                ...
        }

Clearly, you need to check the docs for your chosen class-builder very, very, carefully on this matter.

The solution I came up with, and which is a bit awkward, is to not use default as part of the attribute's declaration, but to set the default value manually inside BUILD(). So, instead of something like:

        has config_name =>
        (
                default  => '.htlocal.config.conf',
                is       => 'rw',
                required => 0,
        );

        sub BUILD
        {
                my($self)       = @_;
                my($module_dir) = __PACKAGE__ =~ s/::/-/gr;

                # Don't do this. In Moo $self -> config_name() cannot be assumed to have a value (yet).

                my($path) = Path::Class::file(File::HomeDir -> my_dist_config($module_dir), $self -> config_name);

                ...
        }

I chose to do:

        has config_name =>
        (
                is       => 'rw',
                required => 0,
        );

        sub BUILD
        {
                my($self)       = @_;
                my($module_dir) = __PACKAGE__ =~ s/::/-/gr;

                $self -> config('.htlocal.config.conf'); # Init by brute force.

                my($path) = Path::Class::file(File::HomeDir -> my_dist_config($module_dir), $self -> config_name);

                ...
        }

Such is life.

Yes, I know around is available. I discuss it below as part of my comments about Moos.

I can do what I originally planned with Moose because with it the object has been instantiated and initialized before BUILD() is called. Yes, I did test that with a quick patch to Local::Config.

A Moo Trap for the Unwary

BTW, Moo gives you a strange way of specifying 'isa'. If you try:

        has attr =>
        {
                default => sub{return 'str'},
                is      => 'rw',
                isa     => 'Str',
        };

As I mistakenly did by typing stuff as though it were Moose, then the constructor (sic) returns that default value string! Unbelievable! Infuriating :-)! Moo does, admittedly, issue a warning, but it still runs...

Of course, if I was running with the strictures pragma loaded, the warning would be fatal, right? And how is the average beginner meant to know that?

Moos and Role::Tiny

When switching to Moos, planning to combine that with Role::Tiny, I see BUILD() is mentioned in the docs. However, a quick test shows the same holds as for Moo - The object is not initialized before the call to BUILD().

But, Role::Tiny does support around, so if I have a attribute config, I could declare it as, say:

        has config =>
        (
                is       => 'rw',
                required => 0,
        );

and then emulate BUILD with:

        around config => sub{...}

However, when trying to write a tiny module which does a bit but not much more than load a file, this seems to me to be an inappropriate use of the avilable complexity. And it worries me this could be a problem which affects all other modules in this set.

Of course, all this because I wish to use BUILD(). And why shouldn't I? Arranging for a brand new object to be immediately useful, without having to make an extra method call to, say, init(), seems reasonable to me.

Naturally, if you don't have access to the original code, or don't want to patch it, around could be a solution.

Moos and Moos-Role (sic)

Firstly, check the docs for Moos::Role regarding this thingy called Moos-Role.

If I code Local::Config as:

        package Local::Config;
        ...
        use Moos-Role;

        has config =>
        (
                is       => 'rw',
                required => 0,
        );
        ...

I can then code Local::Connector as:

        package Local::Connector;
        ...
        use Moos-Role;

        with 'Local::Config';

        has connector =>
        (
                is       => 'rw',
                required => 0,
        );
        ...

Likewise for Local::Logger. And Local::Config still has BUILD().

But now a new problem arises: Local::Config is purely a role, i.e. it has no new(), so I can't use it in the way I wanted (discussed above), as a class, to copy config files during the installation of modules.

So can I add 'use Moos' to add a new()?

        package Local::Config;
        ...
        use Moos;
        use Moos-Role;

        has config =>
        (
                is       => 'rw',
                required => 0,
        );
        ...

Nope - Now I get syntax errors redefining has and with.

So, this is telling me that I can use Moos to build a class or a role, but not one which functions as both.

Other Matters

The trigger feature

Both Moo and Moos provide a trigger which will be called when the attribute's value is being set, but that does not address the issue of initializing the attribute's value in the first place. Nor does it address the usage of BUILD(). So, I have just ignored it in this article.

I did run a test using a trigger attached to the config attribute, to set the value of the config_name attribute, since the latter is needed to find the path to the config file itself. It worked, but like around, I'm uneasy about relying on such a round-about technique, even if the implementation's code itself is reliable.

namespace::autoclean

None of the modules I talk about mention namespace::autoclean, so I chopped out references to it in the source code, given that I'd used Moose to start with in some modules.

Also, I was using '__PACKAGE__ -> meta -> make_immutable;' at the end of the source, so that got deleted too.

Overriding Some Config Values

What about the issue of loading a config file, but then overriding a few values? Here are some ideas:

o Config::Hash

I did not try this module, but it's a candidate.

o Config::Tree

Next is Config::Tree, but this one has about 22 pre-reqs, uses Moose, and is terribly short of docs. So, no.

o Use version-controlled config files

This means choose the set of values at checkout time, not at run-time.

o Do what I do with Local::Config (but not mentioned above)

Structure the config file like this, and use an entry in the file itself (here host), or a value from the environment (i.e. at run-time), to select a section:

        [global]

        # host:
        # o Specifies which section to use after the [global] section ends.
        # o Values are one of localhost || webhost.
        # o Values are case-sensitive.

        host=localhost

        [localhost]

        Local::Application = .htlocal.application.conf
        ...
        doc_root=/dev/shm/html
        ...

        [webhost]

        # TBA.

My code firstly looks for the section '[global]', and then for the key-value pair 'host=localhost', and thus for the section '[localhost]'.

o Config::IniFiles
o Config::INIPlus

Either of these might suffice, when combined with the host trick above.

Winding Up and Winding Down

After all this experimentation, I've opted for Moo and Moo::Role.

As it happens, the latter is based on Role::Tiny, but that's an implementation detail which doesn't, and shouldn't, matter.

Note: Having tried many combinations of these class-builders, I may in fact have gotten confused about their capabilities or limitations. If so, let me know via the comments. I really do want this article to be a reliable guide.

Happy role-playing!