Perl Hackers Hub

第33回MojoliciousでかんたんWebアプリケーション開発(3)

(1)こちら⁠2)こちらから。

Mojoliciousによる開発の勘所

ここからはMojoliciousを利用したWebアプリケーション開発におけるポイントを解説します。

解説する内容を反映したサンプルコードmojo_wikiを本誌サポートサイトに用意しましたので、参考にしてください。

MojoliciousとMojolicious::Lite

(2)までに作成したmyapp.plや掲示板アプリケーションは、Mojoliciousに含まれるマイクロWebフレームワークMojolicious::Liteを利用したアプリケーションです。ルーティング、コントローラ、ビューのテンプレートすべてを1つのファイルに定義できるので、CGIスクリプトのような感覚で気軽に開発できます。

ですが、アプリケーションが成長して規模が大きくなってきたり拡張性やメンテナンス性が必要になってくると、Mojolicious::Liteでは荷が重くなってきます。このような場合には通常のMojoliciousアプリケーションとして開発を行うことをお勧めします。

Mojoliciousアプリケーションは(2)と同様にmojogenerateで生成できますが、コマンドの引数はlite_appではなくappとなります。

$ mojo generate app

コマンドが完了すると、my_appディレクトリ配下に図5のディレクトリ構造が生成されます。各コンポーネントごとにファイルが分割されるので、Mojolicious::Liteのアプリケーションに比べて保守性、拡張性の高い開発ができます。

図5 Mojoliciousアプリケーションのディレクトリ構造
図5 Mojoliciousアプリケーションのディレクトリ構造

プロトタイピングでMojolicious::Liteアプリケーションを作成し、そのあとにMojoliciousアプリケーションへ移行して開発を継続することもできます。移行ガイドを参考にしてください。

モデルの実装

前述したとおり、Mojoliciousはモデルをサポートしていません。

前節の掲示板アプリケーションの場合は特にほかのインタフェース(コマンドラインツールやテストなど)での処理を必要としなかったため、コントローラにアプリケーションロジックを実装しました。ですが、多くのWebアプリケーションではコントローラ以外のインタフェースでもアプリケーションロジックが必要となることがあるため、アプリケーションロジックをモデルに格納し、コントローラから切り離して実装するのが効果的です。

リスト9は、O/RマッパのTengを利用したモデルの実装例です。

リスト9 モデルの実装例
package MyApp::Model;
use Mojo::Base -strict;

use Carp qw/carp croak/;
use Mojo::Loader qw/find_modules load_class/;
use Teng::Schema::Loader;

load_class $_ for find_modules __PACKAGE__;

my $INIT_ARGS;

sub init {
  my ($class, %args) = (shift, @_);
  if ($INIT_ARGS) {
    carp "already initialized";
  }
  else {
    $INIT_ARGS = \%args;
  }
  return $INIT_ARGS;
}

sub db {
  croak "You should init first" unless $INIT_ARGS;
  state $db = Teng::Schema::Loader->load(
    namespace => 'MyApp::DB',
    %$INIT_ARGS,
  );
}

1;

このモデルを各インタフェースで直接扱ってもよいのですが、SQLによるデータベース操作を抽象化するまでの実装となっているため、それぞれのインタフェースでアプリケーションロジックを少なからず実装しなければなりません。そこでこのモデルを内部的に利用する、次のようなモデルを作成します。

package MyApp::Model::User;
use Mojo::Base -strict;

sub table_name {'user'}

sub all {
  my $class = shift;
  MyApp::Model->db->search(
    table_name,
    {deleted => 0},
    {order_by => 'name'},
  );
}
...

このように実装することでコントローラからアプリケーションロジックを分離し、次のようにほかのイン タフェースでもモデルを利用できます。

MyApp::Model->init(connect_info => ...);
my $users = MyApp::Model::User->all();

パラメータのバリデーション

MojoliciousではパラメータのバリデーションにMojolicious::Validator::Validationを利用できます。

コントローラのvalidationメソッドでインスタンスを取得でき、リクエストパラメータに対してrequiredoptionalなどの各種制約を指定します。

my $v = $c->validation;
$v->required('name')->like(qr/^[a-z]+$/);
$v->optional('group')->in(qw/admin guest/);
return $c->render if $v->has_error;

テンプレートではMojolicious::Plugin::TagHelpersの各種フィールドを使用することで、エラー時の値の復元やfield-with-errorクラスの付与を自動的に行ってくれます。

<%# TagHelpers の使用 %>
%= label_for name => 'Name (required, only a-z)'
%= text_field 'name'

<!-- エラー時に生成されるHTML -->
<label class="field-with-error" ...>...</label>
<input class="field-with-error" value="INVALID NAME" ...>

また、Mojolicious::Plugin::TagHelperscsrf_fieldと組み合わせることでCSRFCross-Site Request Forgeries対策もできます。データの更新を行うような場面では積極的に利用することをお勧めします。

<%# フォーム画面のテンプレートにcsrf_field を追加 %>
%= csrf_field

# コントローラでCSRFトークンをチェック
return $self->reply->not_found
  if $v->csrf_protect->has_error;

ユーザ定義の制約はMojolicious::Validatoradd_checkメソッドで追加します。筆者はモデルでもMojolicious::Validatorを利用するため、これを継承したリスト10のようなValidatorを作成し、次のコードをモデルに追加しています。

package MyApp::Model;
...
use MyApp::Validator;
...
sub validator {
  state $validator = MyApp::Validator->new;
}
リスト10 Mojolicious::Validatorの継承
package MyApp::Validator;
use Mojo::Base 'Mojolicious::Validator';

...

sub new {
  my ($class, @args) = @_;
  my $self = $class->SUPER::new(@args);
  # ユーザ定義の制約を追加
  $self->add_check(not_blank => sub { $_[2] =~ /^\s*$/ })
       ->add_check(not_like => sub { $_[2] =~ $_[3] });
  return $self;
}

...

MojoliciousアプリケーションではvalidatorメソッドでモデルのValidatorインスタンスを登録します。こうすることでモデルとコントローラで共通の制約を扱えます。

use Mojo::Base 'Mojolicious';
use MyApp::Model;
...
sub startup {
  my $self = shift;
  ...
  # Validator を登録
  $self->validator(MyApp::Model->validator);
  ...

アプリケーションのテスト

Mojoliciousアプリケーションは標準でサーバを通さずにリクエストを送るgetコマンドが利用できます。開発中の簡単な動作確認に重宝します。

$ ./script/my_app get /users 'ul.user-list > li' all
$ ./script/my_app get /users.json /users

本格的なテストにはMojo::Testというテスト用のモジュールを利用します。レスポンスヘッダの内容やCSSセレクタ、JSON Pointerを使用したデータの確認、WebSocketによるデータの送受信など、実際にブラウザでアクセスしたようなテストができます。リスト11Mojo::Testを利用したテストコードの一部です。

リスト11 Mojo::Testを利用したテストコード
use Mojo::Base -strict;

use Test::More;
use Test::Mojo;
...
my $t = Test::Mojo->new('MyApp');
...
subtest 'create_user' => sub {
  ...
  my $url = $t->app->url_for('create_user');

  $t->get_ok($url)
    ->status_is(200)
    ->element_exists('input[name=csrf_token]');

  my $csrf_token = $t->tx->res
                     ->dom->at('input[name=csrf_token]')
                     ->attr('value');

  subtest 'post with valid params' => sub {
    my $name = 'newuser';
    $t->post_ok($url, form => {
        csrf_token => $csrf_token,
        name => $newuser,
      })
      ->status_is(302)
      ->header_is(
        Location
         => $t->app->url_for(read_user => {name => $name});
      );
  };
  ...
};

テストの実行にはMojoliciousアプリケーションのtestコマンドを使います。Test::Harnessに付属するproveコマンドでも実行できます。

$ ./script/my_app test
$ prove -lr t

アプリケーションのデプロイ

Mojoliciousアプリケーションはさまざまな方法でデプロイできます。代表的なデプロイ方法を解説します。

ビルトインWebサーバ

Mojoliciousアプリケーションは標準でHTTPとWebSocketをサポートしたWebサーバ機能が組み込まれます。

daemonコマンドではシングルプロセスのWebサーバ、preforkコマンドではプリフォーク型のWebサーバでアプリケーションを実行できます。開発用途やアプリケーション配布後の実行コマンドとして利用します。

$ ./script/my_app daemon
$ ./script/my_app prefork

Morbo、Hypnotoad

Mojoliciousをインストールすると、mojoコマンドのほかにmorbohypnotoadコマンドもインストールされます。

morboはアプリケーションのdaemonコマンドと同じくシングルプロセスのアプリケーションサーバですが、プロジェクトのファイル変更を検知して自動でプロセスを入れ替えます。開発用のサーバとして利用します。

$ morbo script/my_app

hypnotoadはアプリケーションのpreforkコマンドと同じくプリフォーク型のアプリケーションサーバです。ホットデプロイをサポートし、無停止でアプリケーションのアップグレードができます。本番環境での利用にお勧めです。

$ hypnotoad script/my_app

コマンドの再実行だけでホットデプロイ可能
$ hypnotoad script/my_app
Starting hot deployment for Hypnotoad server 31481.

ビルトインWebサーバやmorboがコマンドラインオプションで動作を設定するのに対して、hypnotoadではMojoliciousアプリケーションのconfigで動作を設定します。Mojolicious::Plugin::ConfigMojolicuous::Plugin::JSONConfigでも同様に設定できます。

use Mojolicious::Lite;
app->config(hypnotoad => ['http://*:80']);
...
app->start;

PSGI、CGI

Mojolicious はPSGIPerl Web Server Gateway Interfaceに対応しています。plackupなどのPSGIをサポートしたコマンドにスクリプトのパスを渡すだけで、PSGIアプリケーションとして実行できます。

$ plackup -S Starman ./script/my_app

またCGIにも対応しており、そのままでCGIスクリプトとして実行できます。

ScriptAlias / /path/to/my_app/script/my_app/

まとめ

駆け足になりましたが、Mojoliciousの概要と開発するうえでの勘所を解説しました。

Mojoliciousは、本稿では解説できなかった非同期I/Oやプラグインなど、高度なWebアプリケーション開発のための機能も充実しています。ぜひとも手元で動かして、その豊富な機能を堪能してみてください。

さて、次回の執筆者は坪内佑樹さんで、テーマは「DockerによるPerlのWebアプリケーション開発」です。お楽しみに。

おすすめ記事

記事・ニュース一覧