Day 10: Minion Stands Alone
The Minion job queue is an incredibly useful tool, but sometimes I need to use it for non-web projects. So, how can I use Minion without needing a Mojolicious web application?
If I don't have a large enough set of jobs to need the task organization provided by Beam::Minion, and although Minion can be used as a fully stand-alone library, the easiest solution is just to build a Mojolicious app. Lucky for me, a Mojolicious app can be just 2 lines:
use Mojolicious::Lite;
app->start;
Now, if I never run a web daemon, this will never be a website. So, can this be called a web application if nobody ever uses a web browser to access it? 🤔
Add Minion
To use Minion, add the Minion plugin to the app. For this example, I'll use the SQLite Minion backend so that I don't need a separate database process running, but Minion can work across multiple machines if you have a database that accepts remote connections.
plugin Minion => {
SQLite => 'sqlite:' . app->home->child('minion.db'),
};
With the Minion plugin loaded, my application gains some new features:
- I can add Minion tasks (runnable bits of code) with the
minion.add_task
helper - I can enqueue jobs in multiple ways:
- From the command-line with the
minion job
command - From inside the application with the
minion.enqueue
helper - From any Perl script by loading Minion and using the enqueue method
- From the command-line with the
- I can run a Minion worker with the
minion worker
command, which will execute any enqueued jobs
Create a Task
I'll create a task called check_url
to check how long it takes to
download a URL. The
Time::HiRes core module will
give me high resolution times.
#!/usr/bin/env perl
use v5.28;
use Mojolicious::Lite;
use experimental qw( signatures );
use Time::HiRes qw( time );
plugin Minion => {
SQLite => 'sqlite:' . app->home->child('minion.db'),
};
app->minion->add_task(
check_url => sub( $job, $url ) {
my $start = time;
my $tx = $job->app->ua->head( $url );
my $elapsed = time - $start;
$job->app->log->info(
sprintf 'Fetching %s took %.3f seconds', $url, $elapsed
);
# If there's an error loading the web page, fail the job
if ( $tx->error ) {
$job->app->log->error(
sprintf 'Error loading URL (%s): %s (%s)',
$url, @{ $tx->error }{qw( code message )},
);
return $job->fail(
sprintf '%s: %s', @{ $tx->error }{qw( code message )}
);
}
$job->finish( $elapsed );
},
);
app->start;
Enqueuing Jobs
Now that I have a task, I can enqueue some jobs. I can add jobs using
the minion job
command:
$ perl myapp.pl minion job -e check_url -a '["http://mojolicious.org"]'
Or from inside of another Perl script by loading Minion and using the enqueue method:
#!/usr/bin/env perl
use Minion;
my $minion = Minion->new(
SQLite => 'sqlite:minion.db', # The same database as the worker
);
$minion->enqueue(
check_url => ['http://mojolicious.org'],
);
Running a Worker
I've enqueued jobs, but nothing's happening, and nothing will happen
until I run a worker using the minion worker
command:
$ perl myapp.pl minion worker
Once the worker starts up, it will immediately begin processing the jobs I told it to run.
And that's it! I'm using Minion without a Mojolicious web application. View the source of the Minion app and the enqueue.pl script.
Original artwork by Doug Bell, released under CC-BY-SA 4.0. It includes a screenshot of the Mojolicious.org website (fair use), the Mojolicious logo (CC-BY-SA 4.0), and the Minion logo (CC-BY-SA 4.0)
Doug Bell
Doug (preaction) is a long time Perl user. He is the current maintainer of CPAN Testers and the author of many CPAN modules including the Statocles blog engine that powers this site.