[Catalyst] Dispatching based on path and host/domain

Matt Pitts mpitts at a3its.com
Tue Apr 22 03:05:31 BST 2008


> -----Original Message-----
> From: Matt Pitts [mailto:mpitts at a3its.com]
> Sent: Monday, April 21, 2008 9:29 PM
> To: The elegant MVC web framework
> Subject: RE: [Catalyst] Dispatching based on path and host/domain
> 
> > -----Original Message-----
> > From: Curtis Fletcher [mailto:curtis.fletcher at pharmaventures.com]
> > Sent: Monday, April 21, 2008 7:08 PM
> > To: catalyst at lists.scsys.co.uk
> > Subject: [Catalyst] Dispatching based on path and host/domain
> >
> > Hi again guys.
> >
> > I've got a moderate sized Catalyst App in production now which I'm
> > almost happy with :)
> > It responds to one domain at the moment, and I'm pondering how to
> break
> > out into two or more doing mostly the same thing on the same
codebase
> > and hopefully the same app instance.
> >
> > The app has a few "usual" controllers that handle specifics like the
> > e-commerce, admin and soon, some database product access stuff. Any
> URI
> > that fails to match a action defaults to the root controller and is
> > then
> > stripped and handled with a few calls to my model which builds the
> > closest page from CMS style content.
> >
> > E.G.
> >
> > http://mydomain.com/cart/view
> > Ends up calling the Local "view" method of myapp::Controller::Cart
as
> > you might expect but
> > http://mydomain.com/businessunit1/product1/specialofferpage
> >
> > Ends up in the root controller and queries tables that holds dynamic
> > URI-to-page content object mappings which then uses common code to
> > render the page
> >
> > So for the CMS style data, if I add another domain "mydomain2" to
the
> > apache config for my myapp and another field to the UriPage table
for
> > "domain" and I'm almost there.
> > But I'm at a bit of a loss at to what to do about:
> > http://mydomain2.com/cart/view
> > Ending up at myapp::Controller::Cart->view because that domain isn't
> > supposed to have the e-commerce bit.
> >
> > I've been pondering how to make this distinction, maybe there is
> > something I could get the dispatcher to do like:
> >
> > package myapp::Controller::Cart;
> >
> > sub view : Local Domain('mydomain.com')
> > {
> > }
> >
> > Or something with the namespaces so that
> > http://mydomain.com/cart/view got mapped to
> > myapp::mydomain::Controller::Cart->view and
> > http://mydomain2.com/search got mapped to
> > myapp::mydomain2::Controller::Search
> >
> > still making sure that anything that didn't match still ended up in
> the
> > root controller unmangled.
> >
> > What I think I'd like if to be able to do is opt a namespace out of
a
> > particular domain's dispatcher
> > Something like:
> > __PACKAGE__->config->{'opt_out'}->
> > {
> > 	'mydomain2.com' => ['/cart',],
> > 	'mydomain.com' => ['/search',],
> > }
> >
> > That way common actions still work on both domains. Worst comes to
> the
> > worst I'll set config options that disable methods based on ENV
> > variables then run two app instances on the same codebase but I'd
> > prefer
> > to avoid that.
> >
> > Is this approach even sane? any suggestions/pointers/thoughts are
> > welcomed.
> >
> 
> You've probably heard this before on the list, but...
> 
> Ideally, you shouldn't have enough code in your Controllers to justify
> "sharing" the app across domains that need different functionality.
The
> meat of the app should be in the Models, then you can just run
multiple
> Cat apps - one with Cart controllers and one without - that use the
> same
> "shared" Models.
> 
> If you're thinking that you're "stuck with what you've got" think
about
> the time it will take you to implement per-domain dispatching vs. the
> time to extract out functionality to a set of Models. Usually for me,
> "fancy" things like dispatching based on domain take much more time to
> implement than they appear, are more buggy and are harder to maintain.
> Ultimately, you'll end up with more maintainable applications if you
> keep the "sharable" functionality in the Models.
> 
> If you're looking to share the app for other reasons (i.e. hosting
> costs) then I would look at it from a "white-labeling" perspective.
> Rather than have domain information in my Controllers, I would create
> the concept of "sites" inside the app and create a Plugin to interface
> the current site's config interface via something like $c->site. Then
> in
> my Cart controllers I might do something like
> 
> MyApp::Controller::Cart;
> 
> ...
> 
> sub auto : Private {
>     my ( $self, $c ) = @_;
> 
>     if ( ! $c->site->has_function('cart') ) {
>         // something here to drop the request back to your "best
guess"
> logic
>     }
> }
> 
> ...
> 
> 1;
> 
> You could even implement $c->site to use a Model to pull in site
config
> info, which means changes on-the-fly.
> 
> v/r
> -matt pitts

Some improvements to my own idea...

1) Abstract the ideas of "controller functions" and "separate sites" as
separate plugins.

2) Build a ControllerRoles plugin and implement
$c->site->has_function('cart') as
$c->can_access_controller_roles('cart')

3) Build the ControllerRoles plugin so that the PerSiteConfig plugin can
affect the behavior of $c->can_access_controller_roles based on the
current site's settings

3) Implement "sub auto : Private" in a base controller class as

sub auto : Private {
    my ( $self, $c ) = @_;

    my $provides =
self->config->{'Plugin::ControllerRoles'}->{provides_roles};

    if (      $provides
         && ! $c->can_access_controller_roles($provides) ) {
        // something here to drop the request back to your "best guess"
logic
    }
}

and then in a subclass Controller do..

__PACKAGE__->config(
    {
        'Plugin::ControllerRoles' => { provides_roles => [ qw/ cart / ]
},
    }
);

I'm sure this could be even nicer with some Moosification, but I'm not
cool enough to do it off-the-top.

v/r
-matt pitts




More information about the Catalyst mailing list