61
Catalyst Design Patterns YAPC::EU 2016

Catalyst patterns-yapc-eu-2016

Embed Size (px)

Citation preview

Page 1: Catalyst patterns-yapc-eu-2016

Catalyst Design Patterns

YAPC::EU 2016

Page 2: Catalyst patterns-yapc-eu-2016
Page 3: Catalyst patterns-yapc-eu-2016

Catalyst is built around and encourages the use

of design patterns.

Page 4: Catalyst patterns-yapc-eu-2016
Page 5: Catalyst patterns-yapc-eu-2016

– https://en.wikipedia.org/wiki/Software_design_pattern

“Design patterns are formalized best practices that the programmer can use to solve common

problems when designing an application or system.”

Page 6: Catalyst patterns-yapc-eu-2016

Design Patterns in Catalyst

Page 7: Catalyst patterns-yapc-eu-2016

Command

• An object wrapped around a function.

• Encapsulates / abstracts all the information needed to perform an action.

• Separate a reusable activity from class.

• Attach ‘bookkeeping’ and monitoring functions

Page 8: Catalyst patterns-yapc-eu-2016

package MyApp::Controller::User;

use Moose; use MooseX::MethodAttributes;

extends 'Catalyst::Controller';

sub list :Local { my ($self, $c) = @_; }

__PACKAGE__->meta->make_immutable;

Page 9: Catalyst patterns-yapc-eu-2016

package Catalyst::Action;

use Moose;

has [qw/class namespace attributes name code private_path/] => (is=>'ro', ...);

sub execute { ... } sub match { ... } sub list_extra_info { ... }

__PACKAGE__->meta->make_immutable;

Page 10: Catalyst patterns-yapc-eu-2016

Examples

Page 11: Catalyst patterns-yapc-eu-2016

package Catalyst::ActionRole::Scheme;

use Moose::Role;

requires 'match_args', 'match_captures';

around ['match_args','match_captures'] => sub { my ($orig, $self, $c, @args) = @_; my $scheme = lc($c->req->env->{'psgi.url_scheme'}); my $required = lc($self->scheme||’’);

return $scheme eq $required ? $self->$orig($ctx, @args) : 0; };

1;

Page 12: Catalyst patterns-yapc-eu-2016

package MyApp::Controller::User;

use Moose; use MooseX::MethodAttributes;

extends 'Catalyst::Controller';

sub list :Local Does(Scheme) Scheme(https) { my ($self, $c) = @_; }

__PACKAGE__->meta->make_immutable;

Page 13: Catalyst patterns-yapc-eu-2016

package MyApp::Controller::Example;

use Moose; use MooseX::MethodAttributes; use Catalyst::ActionSignatures;

extends 'Catalyst::Controller';

sub test(View $v, Model::User $user) :Local { $v->show_profile($user); }

Page 14: Catalyst patterns-yapc-eu-2016

package MyApp::Controller::Example;

use Moose; use MooseX::MethodAttributes; #use Catalyst::ActionSignatures;

extends 'Catalyst::Controller';

sub test :Local { my ($self, $c, @args) = @_; my $v = $c->view; my $user = $c->model('User');

$v->show_profile($user); }

Page 15: Catalyst patterns-yapc-eu-2016

???

Page 16: Catalyst patterns-yapc-eu-2016

Model - View - Controller

• Implement User Interfaces.

• “Pattern of Patterns”.

• Traditionally used for GUI Desktop applications.

• For classic server side applications its more about separation of job duties.

Page 17: Catalyst patterns-yapc-eu-2016
Page 18: Catalyst patterns-yapc-eu-2016

Examples

Page 19: Catalyst patterns-yapc-eu-2016

package MyApp::Controller::User;

use Moose; use MooseX::MethodAttributes;

extends 'Catalyst::Controller';

sub list :Local { my ($self, $c, @args) = @_; my @users = $c->model(‘Users’)->user_list; $c->forward(‘View::Users’, \@users); }

__PACKAGE__->meta->make_immutable;

Page 20: Catalyst patterns-yapc-eu-2016

package MyApp::Model::Users;

use Moose;

extends ‘Catalyst::Model';

sub user_list { return (qw/ Srinivas Joe Holly

/); }

__PACKAGE__->meta->make_immutable;

Page 21: Catalyst patterns-yapc-eu-2016

package MyApp::View::Users;

use Moose;

extends ‘Catalyst::View';

sub process { my ($self, $c, @users) = @_; my @list = map { “<li>$_</li>”} @users; $c->res->body(<<END);

<html> <head> <title>User List</title> </head>

<body> <ul> @list </ul> </body>

</html> END; }

__PACKAGE__->meta->make_immutable;

Page 22: Catalyst patterns-yapc-eu-2016

???

Page 23: Catalyst patterns-yapc-eu-2016

Chain of Responsibility

• An abstraction that decomposes a complex workflow into discrete objects.

• Promotes loose coupling of design, and simple action commands.

Page 24: Catalyst patterns-yapc-eu-2016

package MyApp::Controller::Root;

use Moose; use MooseX::MethodAttributes;

extends 'Catalyst::Controller';

sub root :Chained(/) PathPart('') CaptureArgs(0) { my ($self, $c) = @_; $c->view('SummaryList', items => $c->model)); }

sub summary_list :GET Chained(root) PathPart('') Args(0) { my ($self, $c) = @_; $c->view->http_ok; }

sub add :POST Chained(root) PathPart('') Args(0) { my ($self, $c) = @_; $c->model('Form::Todo')->is_valid ? $c->view->http_ok : $c->view->http_bad_request; }

Page 25: Catalyst patterns-yapc-eu-2016

???

Page 26: Catalyst patterns-yapc-eu-2016

Catalyst Components

• An abstraction that decouples the creation of required objects from the code that needs those objects.

• Associated with Dependency Injection and Service Locator Patterns.

Page 27: Catalyst patterns-yapc-eu-2016

package MyApp::Controller::User;

use DBI; use DBD::Pg; use MyApp::Schema;

use Moose; use MooseX::MethodAttributes;

extends 'Catalyst::Controller';

sub list :Local { my ($self, $c) = @_; my $schema = MyApp::Schema->connect( "dbi:Pg:dbname=myapp;host=localhost;port=5432", "user", "password");

my $user_rs = $schema->resultset('User'); }

Page 28: Catalyst patterns-yapc-eu-2016

package MyApp::Controller::User;

use DBI; use DBD::Pg; use MyApp::Schema;

use Moose; use MooseX::MethodAttributes;

extends 'Catalyst::Controller';

sub list :Local { my ($self, $c) = @_; my $schema = MyApp::Schema->connect( "dbi:Pg:dbname=myapp;host=localhost;port=5432", "user", "password");

my $user_rs = $c->model(‘User’); }

Page 29: Catalyst patterns-yapc-eu-2016

Decouple instance Creation from Usage!

Page 30: Catalyst patterns-yapc-eu-2016

Program against an Interface not an implementation!

Page 31: Catalyst patterns-yapc-eu-2016

package Catalyst::Component;

use Moose;

#wraps a class accessor sub config { … }

# Called by App->setup_components sub COMPONENT { my ($class, $app, $config) = @_; return $class->new($config) }

# Optional, called with $c->component sub ACCEPT_CONTEXT { my ($self, $c, @args) = @_; }

Page 32: Catalyst patterns-yapc-eu-2016

All Models, Views and Controllers inherit from Catalyst::Component

Page 33: Catalyst patterns-yapc-eu-2016

Component Concepts

• Lifecycle

• Dependency Management

Page 34: Catalyst patterns-yapc-eu-2016

Component Lifecycle

Page 35: Catalyst patterns-yapc-eu-2016

Application Scope

• Instance created once during ‘setup_components’ (via ->COMPONENT)

• Can only depend on literals and other application scoped components.

• Can’t meaningfully pass arguments to ->model / ->view / ->controller.

Page 36: Catalyst patterns-yapc-eu-2016

package MyApp::Model::ApplicationMeta;

use Moose; extends ‘Catalyst::Component';

has 'version' => (is=>'ro', required=>1); has 'license' => (is=>'ro', required=>1); has 'copyright' => (is=>'ro', required=>1);

__PACKAGE__->meta->make_immutable;

Page 37: Catalyst patterns-yapc-eu-2016

package MyApp::Web;

use Catalyst;

__PACKAGE__->config( 'Model::ApplicationMeta' => { version => '1.001', license => 'GLP', copyright => '2016', }, );

__PACKAGE__->setup;

Page 38: Catalyst patterns-yapc-eu-2016

package MyApp::Controller::Meta;

use Moose; use MooseX::MethodAttributes;

extends 'Catalyst::Controller';

sub info :Local { my ($self, $c) = @_; my $meta = $c->model('ApplicationMeta'); $c->log->debug("Application version: ${\$meta->version}"); }

Page 39: Catalyst patterns-yapc-eu-2016

Context Scope• does ACCEPT_CONTEXT, invoked during $c-

>model, $c->view (or even $c->controller).

• Can depend on literals, application scoped components and other context scoped components.

• Allows for the current context to in some way inform the component (provide dependencies, etc.)

Page 40: Catalyst patterns-yapc-eu-2016

package MyApp::Model::ApplicationMeta;

use Moose; extends 'Catalyst::Component';

has 'version' => (is=>'ro', required=>1); has 'license' => (is=>'ro', required=>1); has 'copyright' => (is=>'ro', required=>1); has 'total_requests' => (is=>'ro', required=>0);

sub ACCEPT_CONTEXT { my ($self, $c, @args) = @_; return (ref $self)->new( version => $self->version, license => $self->license, copyright => $self->copyright, total_requests => ref($c)::COUNT); }

__PACKAGE__->meta->make_immutable;

Page 41: Catalyst patterns-yapc-eu-2016

I’d prefer a Factory Component…

Page 42: Catalyst patterns-yapc-eu-2016

package MyApp::Model::ApplicationMetaFactory;

use MyApp::ApplicationMeta; use Moose; extends 'Catalyst::Component';

has 'version' => (is=>'ro', required=>1); has 'license' => (is=>'ro', required=>1); has 'copyright' => (is=>'ro', required=>1);

sub ACCEPT_CONTEXT { my ($self, $c, @args) = @_; return MyApp::ApplicationMeta->new( version => $self->version, license => $self->license, copyright => $self->copyright, total_requests => ref($c)::COUNT); }

__PACKAGE__->meta->make_immutable;

Page 43: Catalyst patterns-yapc-eu-2016

package MyApp::ApplicationMeta;

use Moo;

has 'version' => (is=>'ro', required=>1); has 'license' => (is=>'ro', required=>1); has 'copyright' => (is=>'ro', required=>1); has 'total_requests' => (is=>'ro', required=>1);

1;

Page 44: Catalyst patterns-yapc-eu-2016

• Context scoped components can be used to build any type of scope (session, request, workflow, even application!)

• Allows your component to depend on other components, but out of the box the ‘wiring’ is all manual.

• Watch out for performance issues since you are often creating a lot of objects this way.

Page 45: Catalyst patterns-yapc-eu-2016

‘Context’ Scoping can be used to build any other

type of Scope.

Page 46: Catalyst patterns-yapc-eu-2016

“PerRequest” Lifecycle

Page 47: Catalyst patterns-yapc-eu-2016

package MyApp::Model::ApplicationMetaFactory;

use Moose; use MyApp::ApplicationMeta; use Scalar::Util qw/blessed refaddr/;

extends 'Catalyst::Component';

has [‘version’, ‘license’, ‘copyright’] => (is=>'ro', required=>1);

sub ACCEPT_CONTEXT { my ($self, $c_or_app, @args) = @_; if(blessed $c_or_app) { return $c_or_app->stash->{refaddr $self} ||= $self->return_instance(ref $c_or_app); } else { return $self->return_instance($c_or_app); } }

sub return_instance { my ($self, $app) = @_; return MyApp::ApplicationMeta->new( version => $self->version, license => $self->license, copyright => $self->copyright, total_requests => $app::COUNT); }

Page 48: Catalyst patterns-yapc-eu-2016

package MyApp::Model::ApplicationMetaFactory;

use Moose; use MyApp::ApplicationMeta;

extends 'Catalyst::Component'; with 'Catalyst::Component::InstancePerContext';

has [‘version’, ‘license’, ‘copyright’] => (is=>'ro', required=>1);

sub build_per_context_instance { my ($self, $c_or_app) = @_; my $app = ref($c_or_app) || $c_or_app;

return MyApp::ApplicationMeta->new( version => $self->version, license => $self->license, copyright => $self->copyright, total_requests => $app::COUNT); }

__PACKAGE__->meta->make_immutable;

Page 49: Catalyst patterns-yapc-eu-2016

Lifecycle Questions?

Page 50: Catalyst patterns-yapc-eu-2016

Injecting Dependencies

Page 51: Catalyst patterns-yapc-eu-2016

package MyApp::LoginForm;

use HTML::FormHandler::Moose; extends 'HTML::FormHandler';

has 'user_rs' => ( is=>'ro', required=>1 );

has_field 'name' => ( type => 'Text' ); has_field 'password' => ( type => 'Password' );

1;

Page 52: Catalyst patterns-yapc-eu-2016

package MyApp::Model::LoginForm;

use Moose; use MyApp::LoginForm;

extends 'Catalyst::Component';

sub ACCEPT_CONTEXT { my ($self, $c, @args) = @_; return MyApp::LoginForm->process( user_rs => $c->model(‘Schema::User’), params => $c->req->body_params); }

__PACKAGE__->meta->make_immutable;

Page 53: Catalyst patterns-yapc-eu-2016

package MyApp::Controller::Login;

use Moose; use MooseX::MethodAttributes;

extends 'Catalyst::Controller';

sub process_login :Path('') Args(0) { my ($self, $c) = @_; my $form = $c->model('LoginForm'); # ... }

Page 54: Catalyst patterns-yapc-eu-2016

Best Practice: Don’t write a Model…

Page 55: Catalyst patterns-yapc-eu-2016

…Write a Class and use a Model Adaptor or

Model Injection.

Page 56: Catalyst patterns-yapc-eu-2016

Catalyst::Model::Adaptor

• Encapsulates three common patterns for adapting your class into a Catalyst model or view.

• Application, Factory, PerRequest.

Page 57: Catalyst patterns-yapc-eu-2016

Catalyst::Plugin::InjectionHelpers

• Builds on the component injection introduced in Catalyst version 5.90090.

• Has Application, Factory, PerRequest and PerSession lifecycle adaptors

• Less battle tested that Catalyst::Model::Adaptor

Page 58: Catalyst patterns-yapc-eu-2016

package MyApp;

use Catalyst qw/ InjectionHelpers MapComponentDependencies /;

use Catalyst::Plugin::MapComponentDependencies::Utils ':ALL';

__PACKAGE__->inject_components( 'Model::Schema' => { from_component => 'Catalyst::Model::DBIC::Schema'}, 'Model::Entities' => { from_class => 'MyApp::Entities' }, );

__PACKAGE__->config( 'Model::Entities' => { dbic_schema => FromModel 'Schema' }, 'Model::Schema' => { traits => ['Result', 'SchemaProxy'], schema_class => 'MyApp::Schema', connect_info => [ 'dbi:Pg:dbname=$ENV{DBNAME};host=$ENV{DBHOST};port=5432', '$ENV{DBUSER}', ''], }, );

__PACKAGE__->setup;

Page 59: Catalyst patterns-yapc-eu-2016

package MyApp::Entities;

use Moo;

has 'dbic_schema' => (is=>'ro', required=>1);

# Stuff!

package MyApp::Controller::Example;

use Moose; use MooseX::MethodAttributes;

sub entities :Local { my ($self, $c) = @_; my $e = $c->model('Entities'); }

Page 60: Catalyst patterns-yapc-eu-2016

Could be useful to encapsulate complex

controller logic.

Page 61: Catalyst patterns-yapc-eu-2016

• https://metacpan.org/release/Catalyst-Plugin-MapComponentDependencies

• https://metacpan.org/release/Catalyst-Plugin-InjectionHelpers

• https://metacpan.org/release/Catalyst-Model-Adaptor

• https://metacpan.org/release/Catalyst-Component-InstancePerContext

• https://metacpan.org/release/Catalyst-Model-HTMLFormhandler

• https://metacpan.org/release/Catalyst-View-Text-MicroTemplate-PerRequest

• https://metacpan.org/release/Catalyst-View-JSON-PerRequest

• https://metacpan.org/release/Catalyst-Runtime