Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

Request For Comment: Web Application Plugin Manager

by andreychek (Parson)
on Oct 01, 2002 at 17:25 UTC ( [id://202057]=perlmeditation: print w/replies, xml ) Need Help??

Hello all,

I'm currently working on a module called OpenPlugin. It's a plugin manager for web applications. It allows you to make use of a number of plugins, each which can have any number of drivers. For example, the Log plugin has drivers for logging to STDERR, Files, Syslog, etc. The Session plugin has drivers for storing sessions in Files, DBI, etc. Changing drivers is easy, you just change the driver name in a config file.

OpenPlugin even has plugins for params, cookies, httpheaders, and uploads, which have drivers for Apache and CGI. These plugins abstract Apache::Request and CGI.pm, allowing you to build applications that can work seamlessly under mod_perl or CGI. If you want to move your application from one environment to another, you again can just change the driver being used in the config file.

Also in this config file, you can define whether a plugin loads at startup time, or only on demand.

My question though, it's regarding how you feel about it's interface. Currently, to access plugins under OpenPlugin, you use the syntax OpenPluginObject->PluginName->method. For example:
use OpenPlugin(); my $OP = OpenPlugin->new( config => { src => "somefile.conf" } ); $OP->session->fetch( $session_id ); $OP->param->get_incoming( "param_name" ); $OP->httpheader->send_outgoing();
This Obj->Plugin->method syntax was used for several reasons:

  • Simplicity of code

    The above syntax prevents us from saying, every time we want to use a plugin method for the first time:
    # This seems long my $session_obj = OpenPlugin::Session->new(); $session_obj->fetch( $id );
    Instead of all that, we just need to say:
    # This is short! $OP->session->fetch( $id );
    It's then OpenPlugin's job to make sure the plugin and it's driver are properly loaded, and to return an object of that particular plugin. fetch() is then called on that object.

    If you're just opposed to using the arrow operator twice on one line, you could always say:
    $session_obj = $OP->session; $session_obj->fetch();
  • Centralized Management

    As I mentioned, some plugins are not loaded at startup time, they are only loaded when they are accessed. If we know we won't be using 5 out of the currently 10 plugins, why bother loading them?

    With the above Obj->Plugin->method syntax, OpenPlugin receives a method call for Plugin. If Plugin doesn't exist, OpenPlugin can now do everything it wants/needs to in order to load that plugin, and make it available to the calling application.

    Are there other ways to implement this? Sure, we could have the application author load OpenPlugin::Session first, and then call it's new() constructor. I find this undesirable, I don't think the application author should have to worry about if and when to load a plugin. Another way to do it is to make use of Class::Factory (which OpenPlugin already does internally). Class::Factory would allow us to instanciate a new plugin by saying:
    $session_obj = OpenPlugin->new( 'session', $params ); $session_obj->fetch();
    Class::Factory handles loading the plugin. Much better. But it's still an extra line of code than the existing $OP->session->fetch() offers us.

    Just a little more nitty gritty.

    What happens when I call $OP->session->fetch for the first time? Well, if session wasn't loaded at startup, OpenPlugin has no 'session' method. In fact, OpenPlugin knows nothing about it's plugins, aside from a config file it is given with some configuration information in it.

    So, the first time $OP->session is called, AUTOLOAD ends up being called. AUTOLOAD invokes a method to look up the plugin name (session, in this case) in the config file, determine which driver to load, and then load it. OpenPlugin calls the plugin's constructor, and saves that plugin's object internally in a hash. Lastly, it creates a method called "session" in OpenPlugin's namespace. From here on out, whenever an application calls the "session" method, OpenPlugin returns the session object that we stored.

    The relationship of the various plugins and modules is like so:
    OpenPlugin::Plugin (baseclass for all plugins) | OpenPlugin::Session (session plugin, the parent of the session driver) | OpenPlugin::Session::File (session driver)
    Notice that OpenPlugin.pm is not in this heirarchy. OpenPlugin is a standalone class, it had no parents and no children. OpenPlugin simply offers a means of accessing plugins.

    I bring all this up because I'd like opinions on this interface. I had spoken at length with some folks in the chatterbox yesterday, who felt there might be a better way of doing the above. So, I wanted to explain as best I could what all was going on, and to see what the opinions are on this particular interface. I would very much like to make this available on CPAN, so I wanted to straighten this out first :-)

    For those who are interested, my scratchpad contains the full OpenPlugin.pm file, sample config file entries, and sample usage. Also, if you're interested in trying it out on your system, feel free to msg me and I'd be happy to send you an up to date copy of the full distribution.

    I look forward to any comments, ideas, and constructive criticism. Even though it seems longer, would you still prefer to call a new() method on each plugin? Is there something I didn't mention that you would like even better? And if you like it as is, please let me know that too :-) Thanks!

    -Eric

    --
    Lucy: "What happens if you practice the piano for 20 years and then end up not being rich and famous?"
    Schroeder: "The joy is in the playing."

    edited: Wed Oct 2 01:54:38 2002 by jeffa - added <readmore> tag

  • Replies are listed 'Best First'.
    Re: Request For Comment: Web Application Plugin Manager
    by Zaxo (Archbishop) on Oct 02, 2002 at 09:15 UTC

      I've looked this over and tried to get my head around it. I think that the $factory->class_constructor->method($foo) syntax is better, as you indicated in cb, with parens on the constructor to tell perl and everybody that it is a function: $factory->class_constructor()->method($foo). With that, it doesn't look so odd to me.

      I'm not familiar with Class::Factory, so at first I spent some time wondering why the driver config files weren't simply set up as modules subclassing the more general categories of interface and protocol layer. That is what POE looks like, as an example of a system with some similarities to your architecture.

      Your design grew on me as I studied it, and I'm persuaded to give Class::Factory a closer look.

      After Compline,
      Zaxo

        I spent some time wondering why the driver config files weren't simply set up as modules subclassing the more general categories of interface and protocol layer

        Well, if I understand what you mean here, I think the answer is that drivers in OpenPlugin typically *are* subclasses of more generic Plugin modules, and Class::Factory helps me do that.

        Class::Factory is about being able to load/use classes dynamicly. So even with having the drivers as subclasses, we still don't necessarily want to load every plugin or driver at once. There are cases though when we would want to load a plugin or driver at load time, and Class::Factory accommodates us there too.

        Each plugin uses Class::Factory as a base class (actually, more technically, each Plugin uses OpenPlugin::Plugin as a base class, and then OpenPlugin::Plugin uses Class::Factory as it's own base class). Class::Factory then provides a consistant method for loading each plugin via it's new() constructor. So, new() is part of Class::Factory, but inside that constructor is a call to init(). A plugin or driver may implement init() if it wants to do any custom initialization stuff.

        I'll have to take a look at POE to see how they're working things.

        Thanks for your thoughts,
        -Eric


        --
        Lucy: "What happens if you practice the piano for 20 years and then end up not being rich and famous?"
        Schroeder: "The joy is in the playing."
    Re: Request For Comment: Web Application Plugin Manager
    by uwevoelker (Pilgrim) on Oct 02, 2002 at 09:01 UTC
      Hello Eric,

      for me the syntax $op->session->method seems fine. What I not like is get_incoming as method for param. It is not intuitive for me. What do you think about a simple 'get'?

      I /msg you my mail. Please send me the distribution.

      Thanks, Uwe
        What I not like is get_incoming as method for param.

        Good question.

        Lets look away from the Param plugin for a moment, and take a look at the httpheader plugin. With headers, there is a stage where the browser sends headers to the server, and there is a stage where the server sends headers to the browser. To accommodate all of these stages, the httpheader plugin offers the following methods:

      • get_incoming()

      • set_incoming()

      • get_outgoing()

      • set_outgoing()

        The Cookie plugin's interface is the same as the Httpheader one I just described.

        Now, lets look at the Params plugin again. You said that the get_incoming() did not seem intuitive. The reason it's like that though is to match the style of the HttpHeader and Cookie plugins. These two plugins couldn't just have a get() method, as they need to differentiate between incoming and outgoing parameters. So, to keep a like style, I named the methods in the Param plugin get_incoming and set_incoming. I feel that by having a like interface across the plugins, it would make OpenPlugin as a whole easier to learn.

        However, because get_incoming() does seem a bit long, I've been thinking about writing a wrapper function which would work a bit like CGI's or Apache::Request's param() function. Instead of using get_incoming or set_incoming, you could just use incoming(). Behind the scenes, it would call get_incoming or set_incoming based on the amount of parameters you send it. For example:
        # Retrieve incoming param (instead of using (get_incoming) $OP->param->incoming( 'param_name' ); # Set an incoming param (instead of using set_incoming) $OP->param->incoming( 'param_name', 'value' );
        Again, if I add this, both would still work. If you prefer using get_incoming/set_incoming, us them, if you prefer just using incoming(), you can use that.

        Any thoughts?

        -Eric

        --
        Lucy: "What happens if you practice the piano for 20 years and then end up not being rich and famous?"
        Schroeder: "The joy is in the playing."
          Okay, the same syntax (here: method names) on similiar plugins is fine. Maybe you then can add 'get' as an alias for get_incoming, it is more DWIM. :-)

          As a side note: When do you use set_incoming (or $cgi->param('field', $value))? I only use it as "get". The values for the fields are passed to the templating engine.

          Where is the problem for Perl 5 compatibility?

          Bye, Uwe

    Log In?
    Username:
    Password:

    What's my password?
    Create A New User
    Domain Nodelet?
    Node Status?
    node history
    Node Type: perlmeditation [id://202057]
    Approved by footpad
    Front-paged by RhetTbull
    help
    Chatterbox?
    and the web crawler heard nothing...

    How do I use this?Last hourOther CB clients
    Other Users?
    Others sharing their wisdom with the Monastery: (4)
    As of 2024-03-19 05:04 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      No recent polls found