May 05, 2005

Perl の MVC フレームワーク Catalyst に入門してみた

[ Perl ]

近頃の Web + DB なアプリケーションは MVC でモデルは O/R マッピング、みたいなアーキテクチャが主流です。その際 MVC フレームワークを使って作るのはいわずもがなですが、最近 Ruby 界隈(?)では Ruby on Rails、Perl 界隈では Catalyst というのが熱い模様です。Java 界隈では Spring が熱いのかな?

Perl の O/R マッピングのデファクトは多分 Class::DBI で、Class::DBI と相性が良いテンプレートエンジンと言えば Template-Toolkit。という感じで、自然とモデルとビューに何を使うかは決まってきます。そこであとはコントローラ、というわけですが、Catalyst は主にそのコントローラの部分です。CDBI + TT なアプリケーション向けのコントローラですが、モデルやテンプレートは CDBI と TT に限定されてるわけではないので、別の組み合わせでも使えます。

と、いうことでちょっとお勉強がてらいじってみました。以下、そのメモというか解説です。Perl でウェブアプリケーション作りたいけど良いフレームワークはないかなあ、とお嘆きの方は一読していただければこれ幸い。

何はなくともまずは準備から。CPAN から Catalyst モジュールをインストールします。

[naoya@colinux naoya]$ sudo perl -MCPAN -e 'install Bundle::Catalyst'
[naoya@colinux naoya]$ sudo perl -MCPAN -e 'install Bundle::Catalyst::Everything'

これで Catalyst に必要なモジュールは全部入る...といいたいところですがデータベースに何を使うかによって、自動では入ってくれないものもいくつかあります。MySQL を使うなら Class::DBI::Loader::mysql、Class::DBI::mysql あたりを明示的にインストールする必要があるかも。(もちろん DBD::mysql も。)

普通ならここでいったんフレームワークのインストール作業は終えて、ウェブサーバーのセットアップとかに入るところなんですが、Catalyst にはデバッグ用の、コマンドラインから実行できる httpd が付属してくるのでその必要はないです。これが結構便利です。

さて、何を作ろうか...というところですがとりあえずお勉強目的なので qootas.org でも取り上げられている TinyURL っぽいものを作ってみました。qootas.org の記事では Catalyst のバージョン 4.34 を使っていますが、最新の 5.10 ではコントローラ周りの API が結構変わってたりするので、同じようなものを作るのでも調べごとが必要そうですし、題材としては良さそだった、ということで。(なので記事の中身は qootas.org のそれとかなり被っています。)

仕様としては

  • / にアクセスするとURLを入力するフォームがある
  • フォームにURLを入力して submit すると、その URL を縮めた URL が返ってくる
  • 元のURLと短縮URLは、データベース上でマッピングされており、短縮URLへアクセスした場合はそれを見て元のURLにリダイレクトする

といったところです。本物の TinyURL は短縮URL の識別子にランダムな文字列を使っていますが、ここでは簡単のため http://.../23 といった感じで auto_increment な数値を使うことにしました。

あらかじめ、MySQL 上にこんな感じのテーブルを作成しておきます。データベース名は tinyurl、テーブル名は urlmap です。

mysql> desc urlmap;
+-------+------------------+------+-----+---------+----------------+
| Field | Type             | Null | Key | Default | Extra          |
+-------+------------------+------+-----+---------+----------------+
| id    | int(10) unsigned |      | PRI | NULL    | auto_increment |
| url   | varchar(255)     |      |     |         |                |
+-------+------------------+------+-----+---------+----------------+

ここから Catalyst を使った開発に入ります。最初にウェブアプリケーションのパッケージ名を決めます。ここでは NDO::TinyURL としました。パッケージ名を決めたら、catalyst.pl を実行します。すると、各種ディレクトリや Helper スクリプトが生成されます。

[naoya@colinux naoya]$ catalyst.pl NDO::TinyURL
created "NDO-TinyURL"
created "NDO-TinyURL/script"
created "NDO-TinyURL/lib"
created "NDO-TinyURL/root"
created "NDO-TinyURL/t"
created "NDO-TinyURL/t/m"
created "NDO-TinyURL/t/v"
created "NDO-TinyURL/t/c"
created "NDO-TinyURL/lib/NDO/TinyURL"
created "NDO-TinyURL/lib/NDO/TinyURL/M"
created "NDO-TinyURL/lib/NDO/TinyURL/V"
created "NDO-TinyURL/lib/NDO/TinyURL/C"
created "NDO-TinyURL/lib/NDO/TinyURL.pm"
created "NDO-TinyURL/Build.PL"
created "NDO-TinyURL/Makefile.PL"
created "NDO-TinyURL/README"
created "NDO-TinyURL/Changes"
created "NDO-TinyURL/t/01app.t"
created "NDO-TinyURL/t/02pod.t"
created "NDO-TinyURL/t/03podcoverage.t"
created "NDO-TinyURL/script/ndo_tinyurl_cgi.pl"
created "NDO-TinyURL/script/ndo_tinyurl_fcgi.pl"
created "NDO-TinyURL/script/ndo_tinyurl_server.pl"
created "NDO-TinyURL/script/ndo_tinyurl_test.pl"
created "NDO-TinyURL/script/ndo_tinyurl_create.pl"

次に、これは結構お約束な作業っぽいですが、Template-Toolkit (TT) をベースにしたビュークラスを作成します。といってもこれも Helper スクリプトを実行するだけ。

[naoya@colinux naoya]$ cd NDO-TinyURL/
[naoya@colinux NDO-TinyURL]$ script/ndo_tinyurl_create.pl view TT TT
created "/home/naoya/NDO-TinyURL/script/../lib/NDO/TinyURL/V/TT.pm"
created "/home/naoya/NDO-TinyURL/script/../t/v/tt.t"

引数には "view TT TT" としてます。(なんか他の人のを見てても TT TT としてるのが多かったので。) テンプレートエンジンに TT 以外の物を使う場合とかは "view MyViewer" とすると MyViewer.pm がスケルトンとして生成されるのでそこにコードを書いていくといいみたいです。

なお、この Helper で生成された NDO::TinyURL::V::TT クラスは Catalyst::View::TT を継承しているだけのクラスになります。Template-Toolkit で提供されている以上の機能をビューに追加したい場合とかは、このクラスにメソッドを定義していくと良いでしょう。

ビューの下準備はこれでおしまい。以降、ビューの開発は、コントローラを作りながらウェブアプリケーションの各アクションに合わせてテンプレートを書いていく作業になります。

次はモデルの作成です。通常 Class::DBI でモデルを作る場合は、データソースやテーブルのスキーマに合わせて、テーブル一つにたいして Class::DBI のサブクラスを一つ定義していく、ということをやりますが、Catalyst ではやはりここも Helper スクリプトにお任せできます。

[naoya@colinux NDO-TinyURL]$ script/ndo_tinyurl_create.pl model CDBI CDBI DBI:mysql:tinyurl nobody nobody
created "/home/naoya/NDO-TinyURL/script/../lib/NDO/TinyURL/M/CDBI.pm"
created "/home/naoya/NDO-TinyURL/script/../lib/NDO/TinyURL/M/CDBI"
created "/home/naoya/NDO-TinyURL/script/../lib/NDO/TinyURL/M/CDBI/Urlmap.pm"
created "/home/naoya/NDO-TinyURL/script/../t/m/cdbi_urlmap.t"

うーん、楽ちん。"model モデルのパッケージ名 モデルに使うクラス DSN [username] [password]" として実行します。まあ、Class::DBI を使うならお約束的に "model CDBI CDBI DBI:mysql:foobar user pass" でしょうか。

これで下準備は完了。作ったモデルを組み合わせてウェブアプリケーションを作成...つまりコントローラを作っていきます。コントローラのクラスは lib/NDO/TinyURL.pm です。スケルトンがあらかじめ生成されているので、それを書き換えていきます。

ちなみに、今回は / と /12345 と /make の3つしかアクションがないアプリケーションで、いずれも / 直下なので NDO/TinyURL.pm を編集していますが、一階層下って /foobar/action, /foobar/action2, /foobar/action3...なんてことをやる場合は

$ script/ndo_tinyurl_create.pl controller FooBar

として /foobar/ 以下のコントローラのスケルトンを生成し、lib/NDO/TinyURL/C/FooBar.pm を編集します。antipop2.0 - Catalyst で作る簡単 Web アプリケーション: Feed2JS 解説 にはその具体例が載ってます。

話がそれました。コントローラのコードはこんな感じになりました。バージョン 5.10 では sub action : Attribute とかしてメソッドを定義したりします。なんか Perl の Syntax 的に風変わりですが。この辺も詳しくは quootas.org : Catalyst入門: Actionの定義とその処理の流れ(前編) で解説されてたりします。

package NDO::TinyURL;
 
use strict;
use Catalyst qw/-Debug/;
 
our $VERSION = '0.01';
 
NDO::TinyURL->config( 
    name => 'TinyURL',
    root => '/home/naoya/NDO-TinyURL/root',
    templates => {
        index => 'templates/index.tt',
        make  => 'templates/make.tt',
    }
);
 
NDO::TinyURL->setup;
 
sub default : Private {
    my ( $self, $c ) = @_;
    $c->stash->{template} = NDO::TinyURL->config->{templates}->{index};
    $c->forward('NDO::TinyURL::V::TT');
}
 
sub make : Global {
    my ($self, $c) = @_;
    my $url = $c->req->param('url') or     $c->res->redirect('/');
    $url = 'http://' . $url unless $url =~ /^http(?s):\/\//i;
    my $map = NDO::TinyURL::M::CDBI::Urlmap->find_or_create({ url => $url});
    $c->stash->{map} = $map;
    $c->stash->{template} = NDO::TinyURL->config->{templates}->{make};
    $c->forward('NDO::TinyURL::V::TT');
}
 
sub redirect : Regex('^(\d+)$') {
    my ($self, $c) = @_;
    my $map = NDO::TinyURL::M::CDBI::Urlmap->retrieve($c->req->snippets->[0]);
    $map ? $c->res->redirect($map->url) : $c->res->redirect('/');
}

まず、/ にリクエストが来た場合を考えます。/ は default アクションになります。特に何もせずフォームのある画面を表示したいので、

sub default : Private {
    my ( $self, $c ) = @_;
    $c->stash->{template} = NDO::TinyURL->config->{templates}->{index};
    $c->forward('NDO::TinyURL::V::TT');
}

として、$c->forward('NDO::TinyURL::V::TT') を実行します。$c は Context オブジェクトで、コントローラでは基本的にこの Context オブジェクトのメソッドを呼び出してほげほげ、ということをします。ここでは先に Helper スクリプトで用意したビュークラスに、処理の転送を行っています。

転送する前に $c->stash->{template} に、このアクション用のテンプレートファイルのパスをセットしています。なんか、テンプレートファイルのパスを stash に保存しておくというのがちょっと気持ちわるい気もしますが、そういうことみたいです。なお、stash や config にセットしておいた値はテンプレート側から自由にアクセス可能です。

/ 用のテンプレートは templates/index.tt と指定したので、それを用意します。実際の場所は root/templates/index.tt です。

<html>
<head></head>
<body>
<h1>[% name %]</h1>
 
<form action="make" method="post">
URL: <input type="text" name="url" size="30">
<input type="submit" value="tinyurl" type="submit">
</body>
</html>

フォームがあるだけの画面です。[% name %] は TT のテンプレートタグで、TinyURL.pm の中で config にセットしておいた値を展開してます。

これで / は完了。

次は、URL が入力されたら短縮URLを作って画面に表示する /make です。

sub make : Global {
    my ($self, $c) = @_;
    my $url = $c->req->param('url') or $c->res->redirect('/');
    $url = 'http://' . $url unless $url =~ /^http(?s):\/\//i;
    my $map = NDO::TinyURL::M::CDBI::Urlmap->find_or_create({ url => $url});
    $c->stash->{map} = $map;
    $c->stash->{template} = NDO::TinyURL->config->{templates}->{make};
    $c->forward('NDO::TinyURL::V::TT');
}

make : Global と書くと、/make に来たらこのアクションを呼びなさいという指示になります。Global 属性はこんな風に使います。ここでは Class::DBI の find_or_create メソッドで、入力された URL を urlmap テーブルに保存してます。あとは、stash にオブジェクトをセットしつつテンプレートを指定してビューに転送。

なんか url が入力されてなかったときは / に戻すということをやってますが、Catalyst::Plugin::FormValidator あたりを使ったほうが美しいかも。(Catalyst はエラーハンドリングに関する機能はフレームワークに組み込まれてないのかな、誰か教えて。)

root/templates/make.tt はこんな感じです。

<html>
<head></head>
<body>
<h1>[% name %] created</h2>
 
<p><a href="[% map.url %]" target="_blank">[% map.url %]</a> to <a href="/[% map.id %]" target="_blank">[% base %][% map.id %]</a></p>
</ul>
</body>
</html>

最後に、/12345 に来たときにリダイレクトするアクション。

sub redirect : Regex('^(\d+)$') {
    my ($self, $c) = @_;
    my $map = NDO::TinyURL::M::CDBI::Urlmap->retrieve($c->req->snippets->[0]);
    $map ? $c->res->redirect($map->url) : $c->res->redirect('/');
}

ここでは /make のように、アクションの名前は固定じゃなく変数である数値なのですが、このように Regex 属性を使うことでそれを実現できます。正規表現メモリに入れた変数($1) は Request オブジェクトの snippet メソッドで取り出せます。

これでアプリケーションは完成。Catalyst に付属の httpd でテストしましょう。

[naoya@colinux NDO-TinyURL]$ script/ndo_tinyurl_server.pl
[Thu May  5 11:18:10 2005] [catalyst] [debug] Debug messages enabled
[Thu May  5 11:18:10 2005] [catalyst] [debug] Loaded dispatcher "Catalyst::Dispatcher"
[Thu May  5 11:18:10 2005] [catalyst] [debug] Loaded engine "Catalyst::Engine::HTTP"
[Thu May  5 11:18:10 2005] [catalyst] [debug] Found home "/home/naoya/NDO-TinyURL/script/.."
[Thu May  5 11:18:10 2005] [catalyst] [debug] Loaded tables "urlmap"
[Thu May  5 11:18:11 2005] [catalyst] [debug] Loaded components
.=----------------------------------------------------------------------------=.
| NDO::TinyURL::M::CDBI                                                       |
| NDO::TinyURL::V::TT                                                         |
| NDO::TinyURL::M::CDBI::Urlmap                                               |
'=----------------------------------------------------------------------------='
 
[Thu May  5 11:18:11 2005] [catalyst] [debug] Loaded private actions
.=-------------------------------------+--------------------------------------=.
| Private                              | Class                                 |
|=-------------------------------------+--------------------------------------=|
| /make                                | NDO::TinyURL                          |
| /redirect                            | NDO::TinyURL                          |
| /default                             | NDO::TinyURL                          |
'=-------------------------------------+--------------------------------------='
 
[Thu May  5 11:18:11 2005] [catalyst] [debug] Loaded public actions
.=-------------------------------------+--------------------------------------=.
| Public                               | Private                               |
|=-------------------------------------+--------------------------------------=|
| /make                                | /make                                 |
'=-------------------------------------+--------------------------------------='
 
[Thu May  5 11:18:11 2005] [catalyst] [debug] Loaded regex actions
.=-------------------------------------+--------------------------------------=.
| Regex                                | Private                               |
|=-------------------------------------+--------------------------------------=|
| ^(\d+)$                              | /redirect                             |
'=-------------------------------------+--------------------------------------='
 
[Thu May  5 11:18:11 2005] [catalyst] [info] TinyURL powered by Catalyst 5.10
You can connect to your server at http://colinux:3000/

なにやらディスパッチ先はここがこうなってるとかいうステータスが色々出て、サーバーは localhsot の 3000 番にバインドしました。指示通りアクセスすると、

tinyurl_001.gif tinyurl_002.gif

てな感じで画面が遷移し http://colinux:3000/2 にアクセスしたところちゃんとリダイレクトされました。めでたしめでたし。

まだ触りぐらいしか使ってないのですが、とりあえず Helper スクリプトと httpd が超ベンリ。最初引数がよくわかんなかったりしましたが、その辺はフレームワーク学習のためのコストのうちですね。慣れてくると Helper でセットアップ、コントローラとテンプレートをもりもり書いて、必要に応じてロジックをクラスにまとめて...というのの繰り返していくだけでお手軽にウェブアプリケーションを作ることができそうです。

フレームワークのアーキテクチャとしては、一人もしくは少人数向けのフレームワークかなあという印象です。今回正規表現でいじったように、URLとロジックのマッピングをコントローラでかなり柔軟にいじれて自由度が高いのは便利ですが、ちょっと自由度が高すぎるので、少人数ならよさそうだけど、と思った。大規模開発も可能でしょうけど、一番効果を発揮するのは Hacker な人が一人でどんどん作ってくケースかな。

今回ははてなで使っているはてなフレームワークとの比較のために勉強してみました。(アーキテクチャは Catalyst と結構違うんですが)はてなフレームワークは一人よりも少人数開発にフォーカスしたフレームワークで、もう少しプログラマに対する制約が強い。そのおかげでコードが均一になりやすいというメリットが得られてます。その辺の使用感覚と比べて、Catalyst は一人向けかなあと感じた次第。

プラグインでフレームワークそのものの機能を拡張していけるのが良いですね。Sledge もそうだけど、オープンソースなフレームワークはプラグインアーキテクチャでいかに Hacker を刺激するかがその発展の肝かもしれない。(あとドキュメンテーションw)

ちなみに、プラグインを書くには Catalyst::Plugin::Foobar というクラスを作ってその中にメソッドを定義します。ここで定義されたメソッドは Context オブジェクトのメソッドとして定義されます。使うためには use Catalyst qw /Foobar/ とします。プラグインそのものに機能を実装しても良いし、Catalyst::Plugin::Prototype のようにプラグインの中身が更に汎化できる場合は HTML::Prototype のようにそれを外出しのモジュールにしてしまっても良い、といった感じです。

と、いうことで結構使ってみて楽しかったので、次からプライベートで何か作るときは CGI::Application じゃなく Catalyst を使ってみてもいいかな、と思いました。おしまい。

参考にしたドキュメント

Posted by naoya at May 5, 2005 11:51 AM | トラックバック (10)  b_entry.gif
トラックバック [10件]
TrackBack URL: http://mt.bloghackers.net/mt/suck-tbspams.cgi/1549
PHP on...
Excerpt: Perl の MVC フレームワーク Catalyst に入門してみた : ND...
Weblog: p0t
Tracked: May 6, 2005 11:17 AM
RelatedLink
Excerpt: 参考になりそうなURL † ↑フレームワーク † ↑Catalyst † Perl の ...
Weblog: PukiWiki/TrackBack 0.2
Tracked: May 27, 2005 06:47 PM
Catalystはじめました
Excerpt: Perlを使わなきゃならん羽目になったのでMVCフレームワークCatalystをここやここを見ながら触ってみました。 ちなみに僕はJavaではHibernate...
Weblog: hide-k.net#blog
Tracked: June 15, 2005 09:24 PM
Catalystはじめました
Excerpt: Perlを使わなきゃならん羽目になったのでMVCフレームワークCatalystをここやここを見ながら触ってみました。 ちなみに僕はJavaではHibernate...
Weblog: hide-k.net#blog
Tracked: June 15, 2005 09:29 PM
Catalystで簡単な掲示板を作ってみた
Excerpt: Catalystを使ってめちゃめちゃ簡単な掲示板を作ってみました。 (あくまでサ...
Weblog: Drum::Cymbal::Ride Blog
Tracked: June 25, 2005 02:40 AM
use Catalyst qw(初挑戦);
Excerpt: Perl界でじわじわもりあがってきてるMVCウェブフレームワークCatalystを試してみた。Javaのフレームワークの移植かな(Catalinaする人?みたい...
Weblog: Elementary, ...
Tracked: July 10, 2005 11:18 PM
Catalyst
Excerpt: Perl なMVCなのですね。 あっしにはちとつらいかも… CGI::Application さえよくわからないのに…...
Weblog: shunsoku.com
Tracked: September 16, 2005 12:07 AM
Perl 界で今 HOT な MVC フレームワーク Catalyst のインストール方法
Excerpt: 現在もっとも注目されていて評価もかなり高い MVC フレームワークとして Ruby on Rails ってのがあります。Ruby on Rails って方は、「...
Weblog: Drk7jp
Tracked: October 8, 2005 05:40 PM
cheerleader fungible violent signify
Excerpt: Hi
Weblog: inductor 04/01
Tracked: January 4, 2006 08:44 PM
新年だからCatalystでも勉強しよう
Excerpt: 新しい年になったことだし、ちょっと変わったことでもやってみましょうということで、...
Weblog: ヒビノアワ
Tracked: January 6, 2006 05:32 PM
コメント [1件]

index.tt の form タグが閉じてないです。

[1] Posted by: トオルスガルモノノ at May 5, 2005 10:43 PM [返信]
コメントする









名前、アドレスを登録しますか?