Explicit vs Implicit Hooks

At the Koha Hackfest I had several discussions with various colleagues about how to improve the way plugins and hooks are implemented in Koha. I have worked with and implemented various different systems in my ~25 years of Perl, so I have some opinions on this topic.

What are Hooks and Plugins?

When you have some generic piece of code (eg a framework or a big application that will be used by different user groups (like Koha)), people will want to add custom logic to it. But this custom logic will probably not make sense to every user. And you probably don't want all of these weird adaptions in the core code. So you allow users to write their weird adaptions in Plugins, which will be called via Hooks in the core code base. This patter is used by a lot of software, from eg mod_perl/Apache, Media Players to JavaScript frontend frameworks like Vue.

Generally, there are two kinds of Hook philosophies: Explicit Hooks, where you add explicit calls to the hook to the core code; and Implicit Hooks, where some magic is used to call Plugins.

Explicit Hooks

Explicit Hooks are rather easy to understand and implement:

package MyApp::Model::SomeThing;

method create ($args) {
    $self->call_hook("pre_create", $args);

    my $item = $self->resultset("SomeThing")->create( $args );
    
    $self->call_hook("post_create", $args, $item);
    
    return $item;
}

So you have a method create which takes some $args. It first calls the pre_create hook, which could munge the $args. Then it does what the Core implementation wants to do (in this case, create a new item in the database). After that it calls the post_create hook which could do further stuff, but now also has the freshly created database row available.

The big advantage of explicit hooks is that you can immediately see which hook is called when & where. The downside is of course that you have to pepper your code with a lot of explicit calls, which can be very verbose, especially once you add error handling and maybe a way for the hook to tell the core code to abort processing etc. Our nice, compact and easy to understand Perl code will end up looking like Go code (where you have to do error handling after each function call)

Implicit Hooks

Implicit hooks are a bit more magic, because the usually do not need any adaptions to the core code:

package MyApp::Model::SomeThing;

method create ($args) {
    my $item = $self->resultset("Foo")->create( $args );
    return $item
}

There are lots of ways to implement the magic needed.

One well-known one is Object Orientation, where you can "just" provide a subclass which overrides the default method. Of course you will then have to re-implement the whole core method in your subclass, and figure out a way to tell the core system that it should actually use your subclass instead of the default one.

Moose allows for more fine-grained ways to override methods with it's method modifiers like before, after and around. If you also add Roles to the mix (or got all in with Parametric Roles) you can build some very abstract base classes (similar to Interfaces in other languages) and leave the actual implementation as an exercise to the user...

Coincidentally, at the German Perl Workshop Ralf Schwab presented how they used AUTOLOAD and a hierarchy of shadow classes to add a Plugin/Hook system to their Cosmo web shop (which seems to be also wildly installed and around for quite some time). (I haven't seen the talk, only the slides

I have some memories (not sure if fond or nightmarish) of a system I build between 1998 and 2004 which (ab)used the free access Perl provides to the symbol table to use some config data to dynamically generate classes, which could then later by subclassed for even more customization.

But whatever you use to implement them, the big disadvantage of Implicit Hooks is that it is rather hard to figure out when & why each piece of code is called. But to actually and properly use implicit hooks, you will also have to properly structure your code in your core classes into many small methods instead of big 100-line monsters, which also improves testabilty.

So which is better?

Generally, "it depends". But for Koha I think Explicit Hooks are better:

  • For a start, Koha already has a Plugin system using Explicit Hooks. Throwing this away (plus all the existing Plugins using this system) would be madness (unless switching to Implicit Hooks provides very convincing benefits, which I doubt).
  • Koha still has a bunch of rather large methods that do a lot and thus are not very easily subclassed or modified by dynamic code.
  • Koha also calls plugins from what are basically CGI scripts, so sometimes there isn't even a class or method available to modify (though this old code is constantly being modernized)
  • Handling all the corner cases of implicitly called hooks (like needing to call next or SUPER) might be a bit to much for some Plugin authors (who might be more on the librarian-who-can-code-a-bit side of the spectrum then on dev-who-happens-to-work-with-libraries)
  • A large code base like Koha's, which is often worked on by non-core-devs, needs to be greppable. But Implicit Hooks are basically invisible and don't show up when looking through the Core code.
  • But most importantly, because Explicit Hooks are boring technology, and I've learned in the last 10 years that boring > magic!

Using implicit hooks could maybe make sense if Koha

  • is refactored to have all it's code in classes that follow a logic class hierarchy and uses lots of small methods (which should be a goal in itself, independent of the Plugin system)
  • somebody comes up with a smart way to allow Plugin authors to not have to care about all weird corner cases and problems (like MRO and diamond inheritance, Plugin call order, the annoying syntax of things like around in Moose, error handling, ...).

An while it itches me in the fingers to do come up with such a smart system, I think currently dev time is better spend on other issues and improvements.

P.S.: I'm currently traveling trough Albania, so I might be slow to reply to any feedback.