getting big without getting fat, in perl
TRANSCRIPT
Getting big without getting fat, in PerlDean Hamstead, [email protected] PM, 18th August 2015
Whipupitude and Getting Things Done!
● Whipupitude is a Perl strength○ Great at getting from 0 to 1
● Quick perl scripts tend to become enterprise software (possibly better written?)
● Getting Things Done > Unfinished ‘Better’○ A business making money from in-production code
is a business with the luxury of rewriting in-production code!
○ The quality (hence cost) of the solution needs to match the problem being solved
○ (We are artists, so trade offs don’t always sit well)
Going from 1 to 1+● Initially we had nothing, now we have
something● It works well enough that now we have the
luxury of rewriting it● “I was right” attitudes forget that we had to
get from 0 to 1 :)● As our business/platform is now making
money, its a “problem” that demands a better solution
Make the pain go away● Structuring your code to add features easily
& quickly
● Ensure that existing features keep working and new features work as planned
● Providing the right buttons and knobs to make life easier for humans
Plugins 101● Easily add functionality● Easily remove functionality
○ Make things ‘pluggable’!
● Simplify parallel development● Facilitate orthogonal requirements/needs
● Loose coupling and separation of concerns● Simplify testing!
Dog Food Philosophy● State your API publically● Move all functionality into plugins, use only
your stated API● Keep only basic functionality
in the core of the application● Ideally, the core should rarely
change
By using your own API you will quicklydiscover it’s weaknesses!
Measurements of success● Separation of concerns achieved
(I have no idea how you measure this?)● You can change the internals without
changing the plugins!● Others can make a plugin without
understanding all (or any!) internals● It’s so easy (and fun!) to write plugins
that people are doing it whimsically
Hooks● The plugin registers with the program
○ “I can read image/png files”○ “Tell me when the user enters a URL”○ “Call me when a new connection is opened”
● Fine grained control● Helps provide a clear API● Can be a pain to put in hook
calls everywhere it’s(possibly) needed
Hooks as overrides: Net::ServerIn Net::Server you override the default hook handlers (which do nothing). The basic flow is shown below:
$self->configure_hook; # Hook
$self->configure(@_);
$self->post_configure;
$self->post_configure_hook;
$self->pre_bind;
$self->bind;
$self->post_bind_hook;
$self->post_bind;
$self->pre_loop_hook;
$self->loop;
# lots of others inside $self->loop$self->pre_server_close_hook;$self->server_close;
Others (not in ->loop):
$self->can_read_hook()
$self->allow_deny_hook()
$self->request_denied_hook()
$self->post_process_request_hook()
$self->post_client_connection_hook()
$self->other_child_died_hook($pid)
$self->write_to_log_hook()
$self->fatal_hook()
$self->post_child_cleanup_hook()
$self->restart_open_hook()
$self->restart_close_hook()
$self->child_init_hook()
$self->pre_fork_hook()
$self->child_finish_hook()
E.g. Net::Server (cont.)use strict; use warnings;
package My::Thing;
use parent qw( Net::Server::PreFork ); # pick your personality
sub _rename_me { $0 = q|Me: | . $_[0] }
sub post_bind_hook { _rename_me(q/listening/) }
sub process_request { _rename_me(q/running/); do_stuff() }
package main;
My::Thing->run(port => 12345);
The overrides way:● Uses normal perl inheritance● In Net::Server you can override stuff that’s
not intended as a hook for full blown perl madness
● AUTOLOAD can be a lot of fun here● Only allows one sub to be called on each
hook, although your sub could call SUPER::
Class::Trigger● Allows your main program to define hooks (triggers) and
when they are called● Provides methods so that your plugin attaches it’s subs
to these hook points
● Triggers can optionally have callbacks!
● Triggers are inherited!● Triggers are called in the
same order they are attached
DelegationThe program tells the plugin/extension/module: You go take care of this stuff
● “you take care of the database connection”● “you parse the XML”
Not so good for small tweaks and add-ons
Inheritance is / can be a variation of this
Roles● Are when a package (class) is required to
implement certain sub’s (methods)● DIY with sub foo { die ‘missing
foo’ } in your base class (don’t though)● Use Role::Tony, Moo::Role or similar● Both complain early and loud that things are
missing● Plus other helpful stuff
Mix-ins● Add methods into existing base classes● Good for adding new stuff to frameworks● Not so good for changing how something
works● Usually not good for application plugins
The Catalyst framework uses this method, which is how methods magically appear on $c
Flat-file vs ModulesFlat-file● OK for simple stuff● You’ll have to
locate and load them
● Namespace dramas
● Crude entry/exit points
Modules● Full-access to Perl
OO goodness● Perl can take care
of locating them● Elegant touch
points
Flat File & .pm comparedcat Plugins/Example.pl
#!perl
use strict;
use warnings;
sub _hello {
print qq|hello!\n|
}
# just one action, final statement
sub { _hello() }
cat Plugins/Example.pm
#!perl
use strict;
use warnings;
package App::Plugins::Example;
our $VERSION = 0.1;
sub _hello {
print qq|hello!\n|
}
sub action1 { hello() }
sub action2 { print qq|bai!\n| }
1
Use Module::Pluggable#!perl
package MyClass;use Module::Pluggable;use MyClass;my $mc = MyClass->new();# returns the names of all plugins installed under MyClass::Plugin::*my @plugins = $mc->plugins();
# Or if you want to look in another namespaceuse Module::Pluggable search_path => ['Acme::MyClass::Plugin', 'MyClass::Extend'];
# You can limit the plugins loaded using the except option, either as a string, array ref or regexuse Module::Pluggable except => 'MyClass::Plugin::Foo';
# Or if you want to instantiate each plugin rather than just return the nameuse Module::Pluggable instantiate => 'new';
# Alternatively you can just require the module without instantiating ituse Module::Pluggable require => 1;
M::P::Object for OO#!perl
package MyClass;use Module::Pluggable::Object; my $finder = Module::Pluggable::Object->new(%opts);print "My plugins are: ".join(", ", $finder->plugins)."\n";
Other options as per Module::Pluggable. There are so so many :)
Config (CLI)● GetOpt::LongDoes it need introducing? I am open to alternatives though...
● Pod::UsagePrints a usage message from embedded pod documentation (do things once!)
Config (file)“Config::Any provides a facility for Perl applications and libraries to load configuration data from multiple different file formats. It supports XML, YAML, JSON, Apache-style configuration, Windows INI files, and even Perl code.”
Config (file) (cont.)● Config::General deserves an extra look● Apache style config files● Lots of control over how the config is
interpreted and processed for you:○ (Dis)allow multiple identical options?○ Lower case all names?○ Include directories, globs? over and over?○ Merge duplicates?○ Taint cleaning○ Conversion to boolean (“true” and “false”)○ Much much more!
Config (file) (cont.)Fragmenting config files is really good idea. We know this because Debian does it religiously: What Would Debian Do?
/etc/whatever/conf.d/*conf
Facilitates separation of concerns, works well with plugins
Makes writing helper scripts super easy
Just email me or find me on Facebook
Dear Internet,
Please contact me and tell me where I can do better!
I don’t (yet) know everything
Other good stuff:● Writing Pluggable Software
http://www.slideshare.net/miyagawa/writing-pluggable-software
● Build Easily Extensible Perl Programshttp://www.askbjoernhansen.com/archives/2005/08/Build_Easily_Extensible_Perl_Programs.pdf