My current Perl Ironman Challenge status is: My Ironman Badge

Saturday, October 10, 2009

MooseX::CompileTime::Traits

In developing POEx::WorkerPool, I did a lot of abstraction in order to make it easier to scavenge pieces of it for other uses, and also to allow customization on every level.

Initially, this meant that the classes inside POEx::WorkerPool are simply bare. They consume roles that contain the actual implementation details. This ultimately lets you consume those roles in other projects, gaining full functionality, without subclassing. The second feature of the bare classes is that they contained an import() method that did some magic to allow you to specify traits for those classes. Say you wanted to alter the behavior of how WorkerPool does its queing? You could simply write your own Moose::Role and pass it to 'use' to gain a global altering effect. Rockin.

But, it had its shortcomings. For one, I was doing all of the parsing logic, validation, and role application (with 'with' no less). This broke in several cases. Two, this was the second project to gain this ability, with POEx::Role::SessionInstantiation being the first. What I needed, was a proper encapsulation of this functionality.

And so MooseX::CompileTime::Traits was born. Now, some of you may be asking, why not MooseX::Traits::CompileTime? My ultimate reason for not doing that is that I didn't want people to confuse my module for a subclass of jrockway's module. MooseX::Traits applies traits at runtime using a custom constructor (new_with_traits()). So that means, your traits are actually on a per-instance basis. MooseX::CompileTime::Traits, on the otherhand, affects things at the class level. Globally.

I heard some grumblings that this might be a bad thing, but hear me out. While POEx::WorkerPool had multiple levels of abstraction applied to everything from the subprocess, to the worker in charge of it, to the pool itself, there was no clear way to tell it to do something different. To do that, I'd have to subclass up the chain of things inside POEx::WorkerPool to get the custom behavior I need in the lowest of levels. Perhaps that is a design issue, but the simple solution is to simply apply a trait at compile time without having to subclass a thing. I am not wanting this behavior to be selectively applied to some instances and not others. I want to change the behavior on all instances so it fits my needs without subclassing the entire project, basically.

And I needed to do this for work. This daemon that I am working on needed to do some initialization after the worker subprocess had forked. With MooseX::CompileTime::Traits though, it became easy to provide a role for the GutsLoader to advise some of the default behavior to do what I wanted to do. Now I can successfully invoke code after the fork has taken place without a large amount of work (the role to do this ended up being 10 lines).

So, if you want to give your classes (including the internals) the ability to absorb outside behaviors so that people can customize them while maintaining a very loose coupling, give MooseX::CompileTime::Traits a looksee.

No comments:

Post a Comment