Acme-CPANModulesBundle-Import-PerlDancerAdvent-2018

 view release on metacpan or  search on metacpan

devdata/http_advent.perldancer.org_2018_16  view on Meta::CPAN

    return 0;
}

sub get_invalid_queues( $self, @queues ) {
    my %queue_map;
    @queue_map{ @QUEUE_TYPES } = (); 
    my @invalid_queues = grep !exists $queue_map{ $_ }, @queues;
    return @invalid_queues;
}</pre>

<p>With that in place, it was easy for our <code>queue_job()</code> method to throw an error if the developer tried to add a job to an invalid queue:</p>
<pre class="prettyprint">sub queue_job( $self, $args ) {
    my $job_name = $args-&gt;{ name     } or die "queue_job(): must define job name!";
    my $guid     = $args-&gt;{ guid     } or die "queue_job(): must have GUID to process!";
    my $title    = $args-&gt;{ title    } // $job_name;
    my $queue    = $args-&gt;{ queue    } // 'default';
    my $job_args = $args-&gt;{ job_args };

    die "queue_job(): Invalid job queue '$queue' specified" 
        if $self-&gt;has_invalid_queues( $queue );

devdata/http_advent.perldancer.org_2018_16  view on Meta::CPAN

<p>In our base model class (Moose-based), we would create an attribute for our job runner:</p>
<pre class="prettyprint">has 'job_runner' =&gt; (
    is      =&gt; 'ro',
    isa     =&gt; 'MyJob::JobQueue',
    lazy    =&gt; 1,
    default =&gt; sub( $self ) { return MyJob::JobQueue-&gt;new-&gt;runner; },
);</pre>

<p>And in the models themselves, creating a new queueable task was as easy as:</p>
<pre class="prettyprint">$self-&gt;runner-&gt;add_task( InstantXML =&gt; 
    sub( $job, $request_path, $guid, $company_db, $force, $die_on_error = 0 ) {
        $job-&gt;note( 
            request_path =&gt; $request_path,
            feed_id      =&gt; 2098,
            group        =&gt; $company_db,
        );
        MyJob::Models::FooBar-&gt;new( request_path =&gt; 
          $request_path )-&gt;generate_xml({
            pdf_guid     =&gt; $guid,
            group        =&gt; $company_db,
            force        =&gt; $force,
            die_on_error =&gt; $die_on_error,
        });
});</pre>

<h2><a name="running_jobs"></a>Running Jobs</h2>

<p>Starting a job from Dancer was super easy:</p>
<pre class="prettyprint">use Dancer2;
use MyJob::JobQueue;

sub job_queue {

devdata/http_advent.perldancer.org_2018_16  view on Meta::CPAN

    my $notes = $job-&gt;info-&gt;{ notes };
    my $title = $notes-&gt;{ title };
    my $guid  = $notes-&gt;{ guid };

    $job-&gt;on( spawn =&gt; sub( $job, $pid ) {  
        $0 = "$title $guid";
        $logger-&gt;info( 
            "$title: Created child process $pid for job $id by parent $$ - $guid");
    });
        
    $job-&gt;on( failed =&gt; sub( $job, $error ) {
        chomp $error;
        $logger-&gt;error( $error );
    });
});</pre>

<p>To help us for future capacity planning, we want our workers to tell us if they are running at peak capacity, so log when this event occurs:</p>
<pre class="prettyprint">$worker-&gt;on( busy =&gt; sub( $worker ) {
    my $max = $worker-&gt;status-&gt;{ jobs };
    $logger-&gt;log( "$0: Running at capacity (performing $max jobs)." );
});</pre>

<p>Now, we apply the configuration (read below) to the worker. When the worker starts, it tells us information about how it was configured (this was really useful during development):</p>

devdata/http_advent.perldancer.org_2018_18  view on Meta::CPAN

sub opt_desc {
    return (
        [ 'directory|d', 'Application directory' ],
        # More options...
    );
}

sub validate_args {
    my ( $self, $opt, $args ) = @_;
    $opts-&gt;{'directory'}
        or $self-&gt;usage_error('You did not provide a directory');

    path( $opt-&gt;{'directory'} )-&gt;is_dir
        or $self-&gt;usage_error('Path provided is not a directory');
}

sub execute {
    my ( $self, $opt, $args ) = @_;
    my $dir = $opts-&gt;{'directory'};
    # Implement the application activation
    # (Whatever that means...)
}

1;</pre>

devdata/http_advent.perldancer.org_2018_18  view on Meta::CPAN

sub execute {
  my ( $self, $opt, $args ) = @_;

  # Do whatever you want in this area, before we generate

  # For example, let's make sure the application
  # matches a certain naming convention

  my $app_name = $opt-&gt;{'application'};
  $app_name =~ /^My::Company::App::/
    or $self-&gt;usage_error('App must be prefixed by "My::Company::App");

  # Maybe check we are only scaffolding in a particular directory
  cwd() eq '/opt/my_company/webapps/'
      or $self-&gt;usage_error('Only create apps in our webapps directory');

  # At this point, we can run the original scaffolding
  $self-&gt;SUPER::execute( $opt, $args );

  # Now we finished generating, but we can contineu customizing what we have
}

1;</pre>

<p>Writing your own generation on top of the existing generation allows

devdata/http_advent.perldancer.org_2018_22  view on Meta::CPAN

<p>Way back then, we used to write code to check all of our arguments.</p>
<p>If we had a route that includes some ID, we would check that we
received it and that it matches the type we want. We would then decide
what to do if it doesn't match. Over time, we would clean up and
refactor, and try to reuse the checking code.</p>
<p>For example:</p>
<pre class="prettyprint">use Dancer2;
get '/:id' =&gt; sub {
    my $id = route_parameters-&gt;{'id'};
    if ( $id !~ /^[0-9]+$/ ) {
        send_error 'Bad ID' =&gt; 400;
    }

    # optional
    my $action = query_parameters-&gt;{'action'};
    unless ( defined $action &amp;&amp; length $action ) {
        send_error 'Bad Action' =&gt; 400;
    }

    # use $id and maybe $action
};</pre>

<p>The more parameters we have, the more annoying it is to write these
tests.</p>
<p>But what's more revealing here is that this validation code is not
actually part of our web code. It's input validation <i>for</i> our web
code.</p>

devdata/http_advent.perldancer.org_2018_22  view on Meta::CPAN

    'optional' =&gt; [ 'body', 'sid', 'SHA1' ],
] =&gt; sub {...};</pre>

<p>In this form, the parameter <code>format</code> can be provided either in the
query string or in the body, because your route might be either a
<b>GET</b> or a <b>POST</b>.</p>
<h3><a name="register_type_actions"></a>Register type actions</h3>

<p>Type checking itself is the main role of this plugin, but you can also
control how it behaves.</p>
<p>The default action to perform when a type check fails is to error out,
but you can decide to act differently by registering a different
action.</p>
<pre class="prettyprint">register_type_action 'SoftError' =&gt; sub {
    my ( $self, $details ) = @_;

    warning "Parameter $details-&gt;{'name'} from $details-&gt;{'source'} "
          . "failed checking for type $details-&gt;{'type'}, called "
          . "action $details-&gt;{'action'}";

    return;

devdata/http_advent.perldancer.org_2018_23  view on Meta::CPAN

<pre class="prettyprint"># config.yml (these are the defaults)
engines:
  logger:
    Console::Colored:
      colored_origin: "cyan"
      colored_levels:
        core: "bold bright_white"
        debug: "bold bright_blue"
        info: "bold green"
        warning: "bold yellow"
        error: "bold yellow on_red"
      colored_messages:
        core: "bold bright_white"
        debug: "bold bright_blue"
        info: "bold green"
        warning: "bold yellow"
        error: "bold yellow on_red"</pre>

<img src="/images/2018/23/log-1.png">

<p>The <code>colored_origin</code> refers to the part of the message that shows the package that this
message originated in, as well as the file name and line.</p>
<pre class="prettyprint">&gt;&gt; Dancer2 v0.207000 server 28764 listening on http://0.0.0.0:3000
[main:28764] debug @2018-12-19 20:31:06&gt; Hello World in test.pl l. 6
 ^^^^                                                   ^^^^^^^    ^</pre>

<p>The <code>colored_levels</code> are the log levels themselves.</p>



( run in 0.312 second using v1.01-cache-2.11-cpan-3cd7ad12f66 )