Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

MVC question: way to expose API

by Qiang (Friar)
on May 10, 2008 at 10:18 UTC ( #685846=perlquestion: print w/replies, xml ) Need Help??

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

i am trying to write some code with MVC design in mind. the setup is frontend class, adaptor class, then backend class that does the real work. class methods are used extensively in order to call from one class to another class. here is one example:

package MyApp::Frontend; use MyApp::Adaptor::Report; sub get_report { MyApp::Adaptor::Report->get_report($report_id); } package MyApp::Adaptor::Report; use MyApp::Backend::Report; sub get_report { # some business logic such as check if report exist, etc. MyApp::Backend::Report->get_report(...); }
then the backend could be a csv file, ldap or mysql. the design should work but i am unhappy with so many class method calls. i am wondering if there is a shortcut or better, a OO design pattern that can help me here.

i have seen some catalyst examples and i like the way it look:

$c->model('Adaptor::Report')->get_report(..);

it doesn't use class method and loads the module automatically at compile-time too(based on here ). however reading the catalyst src is too challenge for me.

Replies are listed 'Best First'.
Re: MVC question: way to expose API
by rhesa (Vicar) on May 10, 2008 at 11:33 UTC
    I recently needed such an abstraction as well. I had a hierarchy of classes with lots of class methods, which I'm in the process of refactoring into another shape. Since I didn't know exactly how yet, I started with the simplest thing that could possibly work:
    package Model::Classloader; sub new { my $pkg = shift; return bless {@_}, $pkg } sub class { my $self = shift; my $class = shift; my $fullname = $self->{namespace} . "::$class"; eval "require $fullname;"; return $fullname; }
    The usage is pretty simple: you instantiate a Model::Classloader object, giving it a namespace attribute, and then you can repeatedly call class() on it to get the appropriate package name.

    Here's an example for your code:

    package MyApp::Frontend; use Module::Classloader; our $C = Module::Classloader->new(namespace => 'MyApp::Adaptor'); sub get_report { $C->class('Report')->get_report($report_id); } package MyApp::Adaptor::Report; use Module::Classloader; our $C = Module::Classloader->new(namespace => 'MyApp::Backend'); sub get_report { # some business logic such as check if report exist, etc. $C->class('Report')->get_report(...); }
    This does pretty much exactly the same as your original code, but now the package names are abstracted away. When you decide you need more functionality, you can get it by simply extending the class loader.
Re: MVC question: way to expose API
by roboticus (Chancellor) on May 10, 2008 at 12:16 UTC
    Qiang:

    By the example you've used, you may be trying to put too much into your model. A report would be a view of the model, so you wouldn't use MyApp::Backend::Report. The model should concern itself with ensuring that the data remain in a consistent state WRT business rules, etc. I know that was a one-off example and it may not be what you meant, but I thought I'd mention it. In general, I try to keep a model as simple as possible. So when I'm adding a method, I ask myself ... "Is this necessary? At the right level of abstraction? etc.".

    ...roboticus
      Definitely agreed on keeping view-related things in the view, but IMHO you really want to (as much as possible without breaking the layer abstractions) put as much as you possibly can into the Model layer.

      Think of it this way: the Model layer *is* your application, in an abstract sense. It is your application without any regard for specific output formats or input methods. So if part of your application involves generating summary reports of database data, there probably should be a model class/method which handles report generation.

      However, it should only generate the report data based on abstract input parameters, and not deal with any user parameter input or user output formatting. An appropriate controller would gather user report parameter input from an HTML form or an emailed form (or a voice-prompt UI over a telephone), and an appropriate View would transform the report data into HTML or PDF (or text to speech) or whatever output flavor of the month.

Re: MVC question: way to expose API
by starbolin (Hermit) on May 11, 2008 at 04:43 UTC

    Your frontend class, adaptor class, and backend class should be derived classes of the same base class. Then they can inherit the same get_report() member function. That way get_report() is only declared once.


    s//----->\t/;$~="JAPH";s//\r<$~~/;{s|~$~-|-~$~|||s |-$~~|$~~-|||s,<$~~,<~$~,,s,~$~>,$~~>,, $|=1,select$,,$,,$,,1e-1;print;redo}
Re: MVC question: way to expose API
by Qiang (Friar) on May 12, 2008 at 07:17 UTC
    thanks for the replies. I spent few hours to study the source code of catalyst, catalyst::component and i think i grasped the basic idea of exposing one class into another. here is how i see it work in my case:

    every adaptor class is loaded as a component of the frontend base class. at application startup, every component is required and instantiated via new(). The instances are then stored as a class data of the frontend base class, making them some sort of singleton objects.

    package MyApp::Component; use base qw/Class::Accessor::Fast Class::Data::Inheritable/; 1; package Frontend::Base; use base 'MyApp::Component'; __PACKAGE__->mk_classdata(components); sub load_components { # use module::pluggable # for each adaptor that module::pluggable found # load the adaptor($comp) $class->load_component($comp); } sub load_component { my ($class, $comp) = @_; $class->components->{ $component_name } = $comp->new(); } sub adaptor { my ($class, $model) = @_; return $class->components->{ $component_name }; } 1; package Frontend::Report; use base 'Frontend::Base'; sub do_report { $class->adaptor('Adaptor::Report')->method; } 1;
    also looked at DBIx::Class and it uses Class::C3 and Class::C3::Componentised. i suspect that's another way to accomplish my goal.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://685846]
Approved by FunkyMonk
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (7)
As of 2020-10-22 11:59 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    My favourite web site is:












    Results (225 votes). Check out past polls.

    Notices?