2012 Feed

OX off the Web

OX - 2012-12-21

Web applications of respectable size often do more than simply wait for HTTP requests and serve them. There are periodic tasks managed by cron. You might defer long-running, asynchronous tasks with a job queue managed by TheSchwartz, or q4m, or any of their ilk. Some applications have their administration features written as scripts in a bin/ directory. Maybe the application even has some low-level tests too (...if you're lucky!). You certainly want to reuse the code from your application in these external tools, but if the tools must exist outside the HTTP request lifecycle, how do you achieve that?

You can easily do all these things and more because OX is built on top of Bread::Board, which as you've been discovering these twenty-four days of Advent, is a wonderful substrate for building applications. With OX, you have complete control of your application; it's not hiding inside some shell of a web framework that begrudgingly gives you access to only some of your hard-earned code.

Your PSGI file

The first stop on our journey is investigating the .psgi file you use to run your application. It probably looks something like this:


1: 
2: 
3: 
4: 
5: 

 

use 5.16.0;
use warnings;
use MyApp;

MyApp->new->to_app;

 

That to_app method call returns a PSGI-complaint coderef so that your application can serve web requests. No problem. But notice what to_app is being called on: an instance of MyApp. Since you have an instance of your application, you can of course call any methods you've written in that class. But the real power is that you can also pull out the Bread::Board services your application declares.

Administrative scripts

Let's put that to the test. Say our application has users and we need a script to manage them. Specifically we're going to take a user ID as input and promote that user to a Pro account. Here's what that script might look like:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 

 

use 5.16.0;
use warnings;
use MyApp;

@ARGV == 1 or die "usage: $0 userid\n";
my $id = shift;

my $app = MyApp->new;
my $model = $app->model;
my $user = $model->get_user($id);

if ($user) {
    $user->is_pro(1);
    say "Promoted " . $user->email . " to Pro.";
}
else {
    die "Can't find user $id";
}

 

That's all! It's perfectly straightforward code. Instantiate the application, use its model service, and go from there.

We did not need to explicitly tell OX to initialize the model service, since the model accessor that Bread::Board::Declare generates for MyApp will initialize the model object for you. And of course we did not need to write the code to actually initialize the model service; that code is already there in your application class. As a fringe benefit, OX did not needlessly initialize anything else in the application. Since we're just printing simple output for the sysadmin to confirm that the intended user was upgraded, we did not need to fire up the HTML rendering engine. That means you don't have to initialize any of the view's dependencies, which might require some constructor parameters or config. How annoying would that be if you had to pass in a view_root directory constructor parameter even though you weren't rendering anything?

Job queue

Given that you can reuse any of the services that your application uses, it's pretty easy to sally forth and write any kind of script you need to support your application. Let's extend the previous example a little bit to notify the user over email that they were unexpectedly promoted.

We want to decouple the action that caused the email (the Pro upgrade) from actually going through the motions of sending out that mail, so we'll queue up a notification by adding a job to our job queue. That way, we can have retry logic if there was a temporary delivery failure, we can scale out to multiple servers, we can manage what rate we're pushing out email, and so on. The details of using a job queue are way beyond the scope of this article, and they'll differ greatly based on which backend you choose, but I'll try to give the flavor of the thing here. Most importantly, you can go ahead and give your application some kind of job_queue service. Now everything from the model to the view to external scripts can put jobs into that queue just by declaring a dependency on that job_queue service. You'd then write some other script that processes jobs in the queue just by interacting with MyApp's job_queue. Those jobs can also of course use any dependencies they need, such as the model for pulling more data out of the database, or the view for generating nicely formatted HTML mail. It's all right there hanging off MyApp.

Let's extend the script to put a job in our shiny new queue. All that's changing is we're using MyApp's job_queue service and calling add_job on it.


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 

 

use 5.16.0;
use warnings;
use MyApp;

@ARGV == 1 or die "usage: $0 userid\n";
my $id = shift;

my $app = MyApp->new;
my $model = $app->model;
my $queue = $app->job_queue;
my $user = $model->get_user($id);

if ($user) {
    $user->is_pro(1);

    $queue->add_job(
        type => 'ProUpgradeNotification',
        recipient => $user,
    );

    say "Promoted " . $user->email . " to Pro.";
}
else {
    die "Can't find user $id";
}

 

Again, it can't really get any easier.

Because OX takes care of initializing your dependencies, you don't need to worry about setting up the job queue with a database handle or any other configuration. This sort of thing is why they say dependency injection is like the inverse of garbage collection: You can just use it and go on your merry way without having to worry about the details of hooking everything up correctly (just like garbage collection frees you from having to micromanage object lifecycles). If MyApp's model service is a Singleton, and the job_queue service requires it, then your $model and $queue's model will actually be the same object. That way you don't end up with two database handles. You need to specify that dependency only once: when you declare the job_queue service in MyApp.

The salient point with all of this is that your application is not some sacrosanct, monolithic HTTP engine. You can mix and match the components you need and OX will manage all the dependency resolution for you. No longer do you need to implement a tenth of a dependency injection system in every external script you write. I know from time served in older web frameworks that this was a major pain point, for me at least, so I'm thrilled that OX makes this kind of thing so simple it's obvious.

Gravatar Image This article contributed by: Shawn M Moore <shawn.moore@iinteractive.com>