http://qs321.pair.com?node_id=395427

dragonchild has asked for the wisdom of the Perl Monks concerning the following question:

I have been tasked with rebuilding a system to track widgets (what I'm tracking is irrelevant). This subsystem is an XML-RPC server with a very well-defined API. Whenever a new widget is added (through the add_widget() call), there are potentially N actions that need to be taken after the widget has been successfully inserted into the database. For example, if a green widget is added, a specific person needs to be emailed, or whatever.

Every widget is based on a widget type. Each widget type has a specific number of properties (color, size, etc). The events trigger based on the properties of the widget type.

Now, the events will be pre-made, but there are currently over a hundred events. Some widget types have no events that trigger on them, but some might potentially trigger up to 5 (or more) events. There is no constraint on the number of events the addition of a given widget might trigger.

The current system is built around a huge if-elsif-else statement that is around 500 lines long, most of which is cut'n'pasted. There's only really about 4-5 different types of events, just with different parameters. The business wants to triple the number of widget types being handled, adding at least another 10-20 different types of events. (It's important to note that each event type will take completely different parameters.)

So, it's relatively obvious that the if-elsif-else option isn't viable in the long-term. I was thinking about creating an event system, based on the following high-level design:

  1. The widget gets inserted into the database
  2. There will be a bunch of Event objects/classes, each providing a wants_this() and a handle_this() method.
  3. The XML-RPC server will call each Event's wants_this() method, passing in all the relevant information. If the methodcall returns true, then the handle_this() method will be called, passing in all the relevant information.
  4. The Event will go ahead and do the necessary actions, such as emailing the specific person.

Here's the problem - how do I structure this? Assume that the people who will maintain this are Perl gurus, so any feature is usable. My thoughts are going down the following paths:

It's a messy situation. If I go with objects and a config file, I have to specify the event type and all the necessary parameters. Do I just die if the config file is mis-configured? If I go with the database option, how do I source-control it?

If I go with a class for each specific event, I now have hundreds of classes, most of which will look extremely similar. That sounds even worse than the if-elsif-else option!

Help!

Update: I don't think people are understanding the question I'm trying to ask. I already know I'm going to be using a set of callbacks for the event handlers. Those callbacks are going to register with the add_widget() function in some fashion. My question is how to deal with the hundreds of callbacks I'm going to have to deal with. Read the exchange I had with Joost for what I mean.

Being right, does not endow the right to be rude; politeness costs nothing.
Being unknowing, is not the same as being stupid.
Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Replies are listed 'Best First'.
Re: OT: Design question
by Joost (Canon) on Sep 30, 2004 at 18:18 UTC
    Your names are confusing to me. In my eyes, assuming that the only thing that really determines the event and its parameters is the widget, the widget is the event. So let's call the widget $event, and the events event_handlers. What I would try to make is a main handler like this:

    sub gimme_a_widget { my $event = shift; for my $handler (@event_handlers) { $handler->handle($event); } }

    As you can see the main code doesn't actually try to do anything with the innards of either $event or $handler. I'm just making the easiest interface I can think of, and I'm trying to keep all the implementation details out of the main code.

    Now @event_handlers can contain objects or classes, or a mix of both, it doesn't matter, as long as each of them has a handle() method that accepts an $event (or $widget, if you prefer) parameter.

    This makes the @event_handlers themselves responsible for parsing and processing the widget. You probably don't want to copy a lot of similar code to all the different event_handlers, so the $handler classes can inherit from some base class if that seems useful, or they can share code by calling to some other class. (update: or they can be objects of the same class, as demonstrated further on in the thread)

    As for setting up the classes, let's say all event_handler classes are named Handler::SomethingOrOther, and you put each in its own SomethingOrOther.pm file in the /my/event/handlers/Handler directory:

    my @event_handlers; BEGIN { my $dir = '/my/event/handlers'; use lib $dir; for (</my/event/handlers/Handler/*.pm>) { s#.*/##; s#\.pm$//; eval "use Handler::$_;" or die $@; push @event_handlers,"Handler::$_"; } }
    I'm assuming all @event_handlers are classes and not objects, but if you need to you can replace that push statement with
    push @event_handlers,"Handler::$_"->new();
    to create objects instead.

    Have fun.

    Joost.

      I'm with you so far. The problem I'm having is the maintenance of hundreds of event_handlers (to use your terminology). So, your plan would be to have potentially hundreds of classes in the Handlers directory. However, most of the handlers will be almost exactly the same, differing only minor details. For example, the Handler::GreenWidget emails to green2341@place.com and Handler::BlueWidget emails to blue238882@place.com. And, so on for 80 colors.

      Now, if I put this in a config file, how do I handle that?

      Being right, does not endow the right to be rude; politeness costs nothing.
      Being unknowing, is not the same as being stupid.
      Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
      Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        Ok, well in that case you could have a Handler::Colors that has returns those 80 objects from its new() method (though I would probably call it something else, then, like init()):

        package Handler::Colors; use YAML qw(Load); # or some other module to handle config-like files # set up a bunch of event handlers that # send email depending on colour sub init { my $colors = Load('/color/config'); my @handlers; while (my ($color,$email) = each %$colors) { @handlers = Handler::Colors->new( { color => $color, email => $email } ); } return @handlers; #update: added this line! } # create handler object sub new { my $class = shift; return bless { @_ },$class; } # check for my color and send email if found. sub handle { my ($self,$event) = @_; if ($self->{color} eq $event->color()) { send_mail($self->{email}); } }
        updated: added return @handlers line

        update2: To clarify: this code plugs into the code in my post above, so you can have more of these classes for each different type of action you need to perform. All that's changed there is that the

        push @event_handlers,"Handler::$_";
        line needs to be replaced by
        push @event_handlers,"Handler::$_"->init();
        > For example, the Handler::GreenWidget emails to
        > green2341@place.com and Handler::BlueWidget emails to
        > blue238882@place.com. And, so on for 80 colors.
        If you write a base class Handler::Email and within Handler::GreenWidget you do:
        use base 'Handler::Email'; sub email { 'green2341@place.com' }
        And in Handler::Email you do:
        sub handle { my $email = $self->email; # send email # ... }
        I think you get the point.
        So you don't have to duplicate similar code.
Re: OT: Design question
by BrowserUk (Patriarch) on Sep 30, 2004 at 19:43 UTC

    Sounds like a normalisation problem. Many-to-many relationships between widgets, attributes and events?

    So you need a Widget-Attribute table and an Attribute-Event table.

    To add a new Widget, you add one entry into the W-A table for each attribute the widget has.

    Each Attribute has an entry in the A-E table for each Event that must be triggered by that Attribute.

    For source control, you add a revision number to each entry in each table. You have a single entry table from which you pick up the current production level revision and this is used in your where clauses

    select... from Widget-Attribute as wa, Production as p where .... and wa.revision = p.revision;

    This allows you to add new mappings to either table with a higher-than-production revision level, and test them before updating the Production.revision value to cause the production code to use them. Old mapping can be retained as long as required and removed when not.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
Re: OT: Design question
by KeighleHawk (Scribe) on Sep 30, 2004 at 20:31 UTC
    My suggestion if you want to keep it simple, is to expect a couple rounds of refactoring. Then, instead of splitting your head trying to devise the perfiect design, create a first, simple and "obvious" design. The process of organizing will help you decide, eventually, how best to go.

    Because of this, you don't want to invest in any unneccessary, or heavy technology up front. You also want to limit variables or things that can change easily. Data that can be changed on the fly tends to introduce more instability than "flexibility" into a new system. You even alluded to this by asking how to control the data. So don't bother, just hard code it into your classes. If the data rarely changes, then who cares if it is hard coded? It will run faster anyway.

    As to haveing a ton of itty-bitty classes, don't worry about it. It's not that big a deal. Especially if they are simple. It will actually make coding/debugging easier because you won't have a bunch of hyper-creative, dynamic class construction going on. If there is a problem, there will be a very definitive and easy code path to follow. And again, if the classes don't change often, who cares how many there are?

    Keep in mind, object oriented programming does not necessarily reduce the amount of code you have, it just re-arranges it into something easier to follow. Object oriented DESIGN can lead to code reduction simply because it reduces complexity and redundancy.

    If you keep it simple, hard code everything rather than using config files or databases at first, you will actually get a clearer view of what you have. Going through this process will probably give you a view of the system different from what you currently have. Once THAT happens, and the system is stable, THEN look into more clever solutions such as config files, databases, super-spiffy-class-on-the-fly designs and such.

    Keep in mind, if you are going to take an OO approach, make sure you use OO Design, not just OO syntax. An Object Oriented system must be oriented around the "things" of a system, NOT what those things do or have done to them. So when I hear you talk about event classes I get a little concerned. You don't give enough information for me to decide whether event classes are appropriate or not, but it does seem a little close to being nothing more than procedural programming in OO clothing. eg. "Copy" is not an object. "File" is an object that has a "copy" method.

    I also worry when you say that events are triggered based on the widget properties. Again, not very OO-ish (or was this in reference to the current, not future system?) OO allows for some change in behaviour based on parameters, but this is supposed to be used when a single concept has to be defined differently for different types of parameters. For instance, adding two numbers, two letters, two matricies or two objects are all different, but it is still, in some way, addition. You don't design an OO system to examine the parameters then decide to add, subtract, multiply or just spit back random greek letters.

    Turning the problem on its side like this might give you a different view and might offer up a different solution all together. For instance, maybe an OO approach is not really what you want. Maybe what you really want is an event driven system similar to any GUI. Perhaps an Agent based system? Or perhaps what you really want, what this actually sounds more like, is a rules engine. Define the rules, and yes, you'll have a lot of them, but you let the rules engine decide how to manage them.

    FWIW, you're stated question seems to be whether or not to use a config file or database to manage the changeable parts of similar classes so as to reduce the number of actual classes you have to code. My response is neither. Either go with a lot of simple, hard coded classes, or research an entirely different approach. If you have the guts for it, I suggest doing something entirely different because based on what you wrote, I think you are headed down the wrong path. But I was wrong once before, so take that advice with a grain of salt :-)

      If you keep it simple, hard code everything rather than using config f +iles or databases at first, you will actually get a clearer view of w +hat you have
      I want to second this idea, in this context. It used to be the case that I almost jump each time I see a hardcoded string, now I'm more tolerant, actually manytimes I find it a good idea to hardcode something in the first pass. No software is ever done, constant refactoring is the way to go.
Re: OT: Design question
by tmoertel (Chaplain) on Sep 30, 2004 at 21:27 UTC

    The first task is to understand the problem clearly. Let's see what we can dig out of your description:

    1. You have widgets of various types.
    2. Each type of widget has a set of properties.
    3. Each type of property has an associated set of events that you want to trigger when a widget having a property of that type is inserted.
    4. There are many types of events.

    Most of that is straightforward. The only tricky part is understanding the relationships among property types and the events you want to trigger. Ignoring design and implementation issues for now, let's just create a written specification to capture these relationships. For example, we might have the following:

    PropertyA triggers Event1(a), Event3(b) PropertyB # triggers no events PropertyC triggers Event3(c) ...
    The first line says, "Properties of type A trigger an event of type 1 having parameters a and also trigger an event of type 3 having parameters b." Alternatively, we could specify these relationships in a table, which might make things clearer, and/or easier to maintain:
    Property Event 1 2 3 4 ... ======== =========== A a b B C c ...

    Because these relationships are the crux of your maintenance concerns, we ought to make them easy for humans to specify and change. I would therefore not represent them as code. Rather, I would try to use a representation as close to the specifications above as possible, optimizing for humans instead of the computer. For example, we could use a configuration file similar to our first specification or a database table modeled on the second.

    In either case, our implementation would read the specifications upon initialization and then wire up the relationships accordingly via code generation or other means. The idea is to make the computer do the work, not humans.

    For example, our wiring might be as simple as converting the specifications into a lookup table that maps property names (class name) to event handlers:

    my $property_type_events = { Property::A => [ handler { Event::E1->new(@_) } 'a' , handler { Event::E3->new(@_) } 'b' ] Property::B => [ ], Property::C => [ handler { Event::E3->new(@_) } 'c' ], ... };
    Here, handler is a helper function that takes a block of code that builds an event object of a particular type using the given run-time parameters (represented by placeholders like 'a' and 'b') and returns a function that when called creates and triggers the event:
    sub handler(&@) { my ($event_ctor, @args) = @_; return sub { my $event = $event_ctor->(@args); return $event->trigger(@_); }; }

    Now, just make sure that all of your Widget objects have a get_properties method that returns a list of the properties they have, and it's easy to handle the event triggering for an arbitrary widget:

    sub handle_widget_insertion_events($) { my ($widget) = @_; for my $property ($widget->get_properties) { my $handlers = $property_type_events{ ref($property) }; next unless $handlers; for my $handler (@$handlers) { my $rc = $handler->( "pass trigger args here" ); # do something with result code $rc } } }
    That's the approach that I would take: Make the tricky relationships easy to maintain by tailoring their representation for the purpose. The implementation can then be built automatically from that representation.

    I would repeat the approach for the other parts of the problem where it makes sense. For example, if the relationships among widget types and their properties are complicated, I would create an external specification for them, too, and build code from that.

    Hope this helps.

    Cheers,
    Tom

      It's actually even more complicated than that. There are, say, 5 different widget properties. (It's actually more like 20, and some aren't actually widget properties, but are based on widget properties.) Every widget will have a value for every property. The handlers trigger based on the values for the widget properties, not if the widget has a given property. So, I really need to have a way to ask a given handler if it needs to fire based on a given widget and the values for all the properties. And, the issue is how to maintain in a human-readable fashion all these mappings.

      I do appreciate the fact that you've helped refine the problem for me. It's not about the architecture of triggering events and event handlers. It's really about how to make this system accessible by humans. I need to abstract out the complexity and put it in one place. The complexity is going to be there and I can't wish it away. What I need to do is concentrate it so that it's not spread throughout the code.

      Being right, does not endow the right to be rude; politeness costs nothing.
      Being unknowing, is not the same as being stupid.
      Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
      Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        It's not about the architecture of triggering events and event handlers. It's really about how to make this system accessible by humans.

        Amen.

        Since you need build the app to support ongoing adaptability / expandability, the first thing to do is think about how to optimize, from the perspective of the people who will be doing this, the task of adding / modifying app behaviors.

        That means working out a conceptual model for controlling the app that is easy to explain and understand, and then working out a set of cookbook steps that involve the minimum possible amount of input from the person doing the task -- i.e. if the task is structured so that any single piece of information or logic needs to be supplied in more than once place by a human, you probably haven't got the right design yet.

        That said, I like BrowserUK's take on the matter, which seems to go in the same direction as tmoertel's notion: capture the relations as data rather than as blocks / objects / classes of code. That will generally mean less code, and less work for the humans who handle the "behavior modifications"; those are both good things.

        It's actually even more complicated than that.
        Still, I wouldn't change the approach: Create an external specification that is tailored to capture your application's complicated relationships in a way that is intuitive to humans; then, translate the specification into the desired code, either at run time or during a pre-processing step.
        There are (about 20) different widget properties..., and some aren't actually widget properties, but are based on widget properties.) Every widget will have a value for every property.
        This is important information that will help us design our specification.
        The handlers trigger based on the values for the widget properties, ....
        This, too, is good stuff to know. (Maybe you could tell us what you're really doing?)
        So, I really need to have a way to ask a given handler if it needs to fire based on a given widget and the values for all the properties.
        Not necessarily. You might, for example, be better served by having an object of specialized type whose responsibility it is to manage widget property–to–handler relationships. Then you would ask it what handlers ought to be fired, not the handlers themselves. But that's a decision to be made later. For now, let's just figure out how to specify the relationships.

        If I'm reading your new constraints correctly, the relationship we're concerned with is that between widget properties and handlers, not widgets and handlers. So, let's start out with a simple matrix that enumerates all of the possible points of intersection:

        Property Handler 1 2 3 4 ... ======== =========== A a b B C c ...
        This looks much like what I suggested before, but now we have a new interpretation. Each a or b in the matrix represents a set of conditions that govern whether the associated handler (given by column) may fire for the current widget based on its current value of the associated property (given by row). In other words, we can interpret the column under Handler 3 as saying, Handler 3 will fire for the current widget if the current widget's A property satisfies condition b and its C property satisfies condition c. Our rule, for now, is that all associated conditions must be satisfied in order to fire the handler.

        If that's all the more complicated your relationships are, then we're done. But maybe the all-or-none rule is too limiting. Maybe Handler 3 ought to fire if b(A) or if c(C) is satisfied. Then, a simple matrix will not suffice; we'll need a way of specifying expressions:

        Handler Firing condition ======= ================ 1 a(A) 2 ... 3 b(A) || c(C) ...
        If we filled out this two-column table for all of your handlers, we would have a lot of redundancy because, in your original question, you said that, "there's only really about 4-5 different types of events, just with different parameters." So, let's allow the handlers and conditions to be parameterized (and we'll give the handlers names, while we're at it):
        Handler Firing condition ======= =================== Page("Jim") a(A) Email("foo-alert@...") b(A) || c(C, "foo") Email("bar-alert@...") b(A) || c(C, "bar")
        At this point, we have to be careful that our specification doesn't devolve into a bunch of if-statement equivalents. But I'm going to stop here because I'm just guessing about what you really need and don't know which refinement to make next. I don't have enough information to know whether it would be better, for example, to factor out redundancy in our specification or to leave it in. This is where knowing about what you're really doing would be helpful.

        Depending on your needs, reasonable refinements might include factoring out common subexpressions, adding environmental context, or adding a related specification table. Each involves trading one kind of complexity for another, and whether it makes sense for you depends on the costs of managing different kinds of complexity in your application and programming culture. Only you know enough to make these decisions at this point.

        Cheers,
        Tom

Re: OT: Design question
by FoxtrotUniform (Prior) on Sep 30, 2004 at 20:17 UTC
    There's only really about 4-5 different types of events, just with different parameters.

    This makes me think that this might help.

    --
    F o x t r o t U n i f o r m
    Found a typo in this node? /msg me
    % man 3 strfry

      Yes, the functional programming way of deferring callbacks to other callbacks. I like it :)

      It's really amazing sometimes how powerful divide-and-conquer logic can be. I like it how top-down thinking allows "start with a program definition, break it up, break those problems up, and keep coding until all your functions are implemented".

      If you control what you name your functions and your modules as you go, it can be really beautiful. I have a application-specific SOAP module I wrote, the code is elegant as heck, but I read it and wonder "It's a miracle this thing works as simple as the methods and API are...". Thinks just happen. And they just work. Because you told them to. Neat.

Re: OT: Design question
by jdporter (Paladin) on Sep 30, 2004 at 18:02 UTC
      I'm not having problems with the pattern. I'm having problems with how to implement the pattern. Do I implement just the base classes and have a config file with hundreds of entries or do I implement hundreds of classes? Do I put stuff in a file or a database? How do I maintain the hundreds of Observers when the difference between most of them is just what they're observing and what parameters they pass to the same command when they trigger?

      Being right, does not endow the right to be rude; politeness costs nothing.
      Being unknowing, is not the same as being stupid.
      Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
      Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Re: OT: Design question
by SpanishInquisition (Pilgrim) on Sep 30, 2004 at 20:06 UTC
    Event loops also lend themselves to hashes of anonymous subroutine references (in this case, used as callbacks). Hashes of anonymous subroutines are one of my favorite idioms.

    $callbacks = { 567 => sub { }, 568 => sub { }, 569 => sub { } }; # as an exercise for the reader # if defined event id callback, invoke callback # else do what you will to indicate no handler is # registered, log whatever.
    callbacks can be keyed off of event ID's, names, or some stringification of a class -- it doesn't matter. Anyhow, this is efficient and very fast, much better than lots of if's, and much cleaner than 100 subclasses of a base event class! It's almost like a bucket sort filled with function pointers. Wheee! (You could do this too, use an array instead of a hash, your choice...)

    You may also be interested in POE... but I'm not really a big fan of it.

    If the differences between events are subtle and you don't want to repeat code, it may be issue to have a multi-level set of callbacks, whose purpose it is to route events to different types of more specialized event handlers. (Basically seperate your DeathOfWorld events from your ItsAllGood and PleaseHelpMe and IWantACookie events, and then have the handlers for these commands implement a more simplistic callback mechanism.

    Thinking in terms of patterns alone will get you roped into spending 80% of your time in the design process. Think in C, Think in C, Think in C, Think in C, there will be an answer, Think in C.

Re: OT: Design question
by BrowserUk (Patriarch) on Oct 01, 2004 at 05:45 UTC

    This is rather simplistic, and may look somewhat complex, but note that 1/3rd of the lines are data which could be in a DB or a config file; and half of the code is simply used to generate some test data.

    The entire thing is data-driven. Hand code some more attribute types into the __AE__ section (or add some values in the arrays in GenData package and uncomment the first line of main) and nothing else need to change apart from adding event handlers to suit any new events.

    The event handlers become class data for the widget class. The attribute-event table becomes class data for the attribute class. The rest, hopefully, is self-explanatory.

    Ouputs:

    P:\test>395427 $VAR1 = bless( { 'color' => bless( { 'value' => 'green', 'type' => 'color' }, 'Attribute' ), 'weight' => bless( { 'value' => 1000, 'type' => 'weight' }, 'Attribute' ), 'size' => bless( { 'value' => 13, 'type' => 'size' }, 'Attribute' ), 'material' => bless( { 'value' => 'steel', 'type' => 'material' }, 'Attribute' ) }, 'Widget' ); Sending email to green@somewhere.com Faxing 0800-FAX-1000 Faxing 0800-FAX-13 Sending email to 13@somewhere.com Phoning 0800-CALL-STEEL Faxing 0800-FAX-STEEL Sending email to steel@somewhere.com

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
Re: OT: Design question
by Prior Nacre V (Hermit) on Oct 01, 2004 at 05:12 UTC

    Here's a possible way you might go about this using just two notionally singleton classes.

    I haven't addressed data storage and retrieval, although I suspect a DB solution will prove the better choice. (%CFG represents the config data regardless of where and how it was read.)

    I've also assumed $WID. This is expected to hold some unique reference to the widget being added.

    Consider this as the basis of an approach to a solution rather than the solution itself. I've left it very skeletal as I don't know how this might fit into any existing framework you may have.

    package Widget; sub new { my ($class, $rh_def_cfg) = @_; bless { rh_def_cfg => $rh_def_cfg, ro_event => Event->new } => $cl +ass; } sub do_events { my ($self, $WID) = @_; my $rh_event_data = $self->get_event_data($WID); foreach my $event (@{$rh_event_data->{events}}) { $self->{ro_event}->$event($rh_event_data->{params}); } } sub get_event_data { # Use $WID to get type & widget # Use type to get parameters # Use widget to get events (based on type params) # Include other data to be used in Event class # Return hashref with event data } package Event; sub new { my $class = shift; bless {} => $class } sub email { ... } sub log { ... } sub trigger { ... } package main; my %CFG = get_cfg(); # DB or config file my $ro_widget = Widget->new(\%CFG); sub add_widget { # Add here, then: $ro_widget->do_events($WID); }

    You might also consider an Aspect-Oriented solution. There's an Aspect module on CPAN which may be useful. (I don't know anything further about this module - others might).

    Regards,

    PN5

Re: OT: Design question
by hv (Prior) on Oct 01, 2004 at 11:28 UTC

    I'm confused about names. I would describe the activity you are modelling as "an event triggers an action". In that terminology the "event" is "add a widget to the database"; the long if/elsif statement you're trying to replace is then a combination of the triggers and actions.

    If I understand your description so far, you are conceptually combining the trigger and the action into a single item, and planning to have a class for each combination of type-of-trigger and type-of-action. If so, consider separating the two to reduce the number of classes you have to deal with.

    Beyond that, both trigger and action are in principle a coderef, one that makes a decision (no side effects, returns a boolean), and one that does something (all about the side effects, returns nothing or maybe a failure code). You can probably reduce those to a small set of simple components and a small set of combining rules, like:

    Trigger::property_is ( property_name, value ) returns true if the widget has property I<property_name> equal to +I<value> Trigger::and ( trigger1, trigger2 ) returns true if both trigger1 and trigger2 are true Action::email ( address, template ) fills in I<template> and emails the resulting text to I<address> Action::also ( action1, action2 ) performs I<action1>, and if successful also performs I<action2> etc.

    This can all readily be modelled by a small domain-specific language that will make the configuration nice and easy to read and write.

    However for the numbers you're talking about I'd want to store these things in a database and start thinking about tools to interrogate and act on subsets of the triggers as a whole, and this is where it gets tricky - I've never yet found a good solution to modelling this type of information in a database, in particular where you have lots of similarish functions that nevertheless can take different numbers of parameters, or different types of parameters, or parameters (such as 'value' in the first example above) whose type can only be derived by a complex process (looking up the type of the 'property_name' property).

    Anyone got a polymorphic database field type handy?

    Hugo