Mojoliciousでつくる!
Webアプリ入門
2013/09/21
YAPC::Asia 2013
Yusuke Wada a.k.a. yusukebe
Some papix photos are in this slides.Thanks to papix!
イントロダクション
自己紹介
• 和田裕介 1981/12/23 生
• 慶應義塾大学制作メディア研究科修了
• 株式会社ワディット代表取締役
• 株式会社オモロキ取締役兼最高技術責任者
• http://yusukebe.com/ or @yusukebe
メインワーク
問いかけ
Perlは大規模Webサービス
で使われているが...
初心者がWebアプリに入門する資料が少ない...?
あえて去年を振り返る
「新しい」を生み出すためのWebアプリ開発とその周辺
何をつくるかは分かった
そのための
実装について
本日の主題
対象オーディエンス
• Webアプリケーションをつくりたい方
• アイデアはあるが実装できない... って方
• 最近のWAFについて知りたい方
• Web Application Framework = WAF
• Mojoliciousを使った具体的なアプリ構成を
知りたい方
方針
当たり前に使っている概念、実装、キーワード
噛み砕いて解説
=> GoogleやCPANで検索できるように!
実はすごく周辺からは分かりにくい...?
アジェンダ
• 例題:占いアプリについて
• WebアプリケーションとFramework
• Mojoliciousの紹介
• Mojolicious::Liteを使う
• より実践的なアプリへ
• CPANモジュールとの組み合わせと工夫
• 今後へ
0. 例題:占いアプリについて
名前に応じて占い結果を出す
• 占い結果はまず3種類
• 良い事が起こるでしょう
• 出会いがあるかも?
• 不幸になります
• 入力された名前に対して必ず同じ結果
• yusukebe => 「出会いがあるかも?」
ハッシュ関数
my $name = "yusukebe";
my $max = 3;
my $number = 0;
# 文字列を一文字ずつ分解
for my $char (split //, $name) {
# 文字に対応する数値を得る
$number += ord $char;
}
# 想定される最大値で割った余りを得る
my $result = $number % $max;
print "$resultn";
文字列
数値
固定値で割る
余りが結果
スクリプトで実現する
ようは診断メ○カーみたいな?
1. WebアプリケーションとFramework
Webアプリケーションを定義する
Webが誰かと特徴的な
インタラクション
をする場合
Webアプリケーション
Webのインタラクション
Web
アプリケーション
ユーザー
例えば自分の名前を
入力する
占いの結果を見る
JSの動作
外部のWeb API
ミドルウェア etc.
ブラウザ
プログラム
スマホアプリ
Web Application Framework
• 通称 WAF(ワッフ)
• 例えば Ruby on Rails
• Webアプリケーションをつくるための土台
• MVCモデルに基づく場合が多い
MVCってなんぞー?
Model
View
Controller
MVC
MVCである意味は...?
MVC以前
何がイケてないって?
• 色々混じってる
• ヘッダー出力
• ロジック処理
• HTMLの描画
• ファイルごとにルーティングする
• 重複が起こる
*CGIはデプロイ方法の
一つなのでCGI自体が
良くないわけではない
愚直に.cgiを書くと
use CGI qw/param header/;
my $name = param('name');
my @list = qw/良い事が起こるでしょう 出会いがあるかも? 不幸になります/;
my $num = 0;
$num += ord ($_) for split //, $name;
my $index = $num % scalar @list;
my $message = $list[$index];
print header('text/html; charset=utf-8');
print "<html><body>";
print "<center><b>$message</b></center>";
print "</body></html>";
HTMLだけでも分離させると...
use CGI qw/param header/;
use Text::MicroTemplate qw/render_mt/;
use Data::Section::Simple qw/get_data_section/;
my $name = param('name');
my @list = qw/良い事が起こるでしょう 出会いがあるかも? 不幸になります/;
my $num = 0; $num += ord ($_) for split //, $name;
my $index = $num % scalar @list;
my $message = $list[$index];
my $template = get_data_section('index.mt');
my $html = render_mt($template, $message)->as_string();
print header('text/html; charset=utf-8');
print $html;
__DATA__
@@ index.mt
<html>
<body>
<center><b><?= $_[0] ?></b></center>
</body>
</html>
冗長になるが
見通しがよくなる!
さらにテンプレートをファイルに分けると...
メンテナンス性、再利用性の向上
冗長性はフレームワークが吸収してくれる?
とりあえずMVCで考える意味
• 混在している固まりを役割で分ける
• 理解をすれば冗長になるが開発効率が上がる
"分けるとは分かること"
切り分け方
1. Router
2. Controller
3. Model / Logic
4. DB層 / OR Mapper
5. View / Static files
ビジネスロジックを扱う
出力や見た目に関する部分
個別のリクエストに基づく処理
データベースにまつわる
URL/METHODに応じて振り分ける
役割を分けること
=
フレームワーク的思考へ
処理の流れ
Router
Controller
Model / Logic
DB / OR Mapper
View
1. リクエスト
2. ディスパッチ
3. モデル呼び出し
4. DB呼び出し 5. DB結果返却
6. 結果
7. レンダリング
8. HTMLなど
9. レスポンス
PerlのWAF達
• Catalyst
• Dancer
• Amon2
• Kossy
• Pickles
• Voson
• Mojolicious
• 自作WAF
自作WAFと既存WAF
• MVC?を実現するモジュールは揃っている
• Plack::Request
• Router::Simple
• Text::Xslate
• ただアプリを書きながらWAFもはキツい
• 勉強用に車輪の再発明は多いに歓迎
• でもとりあえずWAFを使いましょうね ♥
2. Mojoliciousの紹介
Mojolicious !
• 属に言う「軽量WAF」の一種
• 最新Ver.は「4.35」2013年9月12日時点
• 作者は「Sebastian Riedel」氏
特徴1. VCだけサポート
• Routing / Controller
• デフォルトではMojo::Templateを使うView
• のみサポートし「Model」は対象外
Mojo::Baseその他でクラスつくったりOR Mapper
使ったりして自力で実装してね ♪ っていう...
逆にそれがいい
Model/Logic層はWebから切り離すべき
特徴2. Not full-stack but full-stack
Ruby on Rails
DBを扱うまで全てこれ一つで出来る フルスタック
Mojolicious
HTTPのRequestやUserAgentまで
Mojoliciousで使うライブラリが全て自作
フルスタック
コア以外のCPANモジュールに依存しない!
$ cpanm Mojolicious
Mojoliciousなら一発
特徴3. その他
• アップデートが頻繁
$self->render_json( $ref ); # 4.00 で廃止
# =>
$self->render( json => $ref );
ただし依存が無いのでMojoliciousだけ追っていればよい
• Mojolicious::Lite を使えばファイル一つで
• 工夫すればCGIでも動作可能
• Perl 5.10.1 以上必須、5.16 以上を推奨
• 開発用サーバmorbo、本番用hypnotoad
• もちろんPSGI互換
小さなアプリから大きなWebアプリまで
徐々に学べるフレームワーク
Mojolicious::Lite
Mojolicious
CPANモジュールの組み合わせ
3. Mojolicious::Liteを使う
mojoコマンドによるスケルトン
$ mojo generate lite_app myapp.pl
Mojolicious::Lite
はじめてのMojolicious::Lite
#!/usr/bin/env perl
use Mojolicious::Lite;
get '/' => sub {
my $self = shift;
$self->stash->{message} = 'Hello Mojo!';
$self->render('index');
};
app->start;
__DATA__
@@ index.html.ep
<html>
<body>
<p><%= $message %></p>
</body>
</html>
Controllerに該当
View(テンプレート)
Routingしてる
立ち上げ方
$ morbo -l "http://*:5000" myapp.pl
morboを使う
$ plackup -p 5000 myapp.pl
plackupで立ち上げる
Hello World から学ぶ
get '/' => sub {};
ルーティングとコントローラ
GET http://localhost:5000/
get '/entry/:id' => sub {
my $entry_id = $self->stash->{id};
};
post '/entry' => sub {};POST http://localhost:5000/entry
GET http://localhost:5000/entry/1
Hello World から学ぶ
get '/' => sub {
my $self = shift;
$self->stash->{message} = 'Hello Mojo';
...;
}
テンプレートにデータを渡す
$self->render('index'); # index.html.ep を描画
...;
<p><%= $message %></p>
テンプレートの記述とレンダー
もちろん占いアプリも!
Short live coding !?
#!/usr/bin/env perl
use Mojolicious::Lite;
use utf8;
...;
get '/' => sub {
my $self = shift;
$self->render('index');
};
...;
@@ index.html.ep
<p>名前を入力してください</p>
<form action="/result">
<input type="text" name="name" />
<input type="submit" value="占う" />
</form>
トップページを描画
...;
get '/result' => sub {
my $self = shift;
my $name = $self->req->param('name');
my @list
= qw/良い事が起こるでしょう 出会いがあるかも? 不幸になります/;
my $num = 0;
$num += ord ($_) for split //, $name;
my $index = $num % scalar @list;
$self->stash->{message} = $list[$index];
$self->stash->{name} = $name;
$self->render('result');
};
...;
@@ result.html.ep
<p><%= $name %>さんの結果「<%= $message %>」</p>
結果を出力
Controller内での操作
• 全ては「$self」のメソッドを扱う
my $value = $self->req->param('key');
$self->render('entry');
$self->render( json => { key => $value } );
Mojolicious::Controller を継承するのでそのドキュメントを読めばOK!
$self->redirect_to('/');
Mojo::Template周り
• Perl-ish templates => Perl書ける!
% for my $entry (@$entries) {
<h2><%= $entry->{title} %></h2>
% }
Mojo::Template / Mojolicious::Plugin::DefaultHelper
• テンプレート向けhelper
@@ index.html.ep
% layout 'default';
Hi, <%= $name %>
@@ layouts/default.html.ep
<html>
<body><%= content %></body>
</html>
Mojolicious::Liteアプリを拡張する
• public/ => 静的ファイルを置く
• templates/ => テンプレートファイルを独立
./
!"" myapp.pl
!"" public/
#   !"" css/
#   !"" favicon.ico
#   !"" images/
#   %"" js/
%"" templates/
!"" layouts/
#   %"" default.html.ep
%"" root/
%"" index.html.ep
4. より実践的なアプリへ
Mojolicious::Liteだと1ファイルがでかくなるよー
そこで ::Lite じゃないMojoliciousアプリ!
モジュールをいい感じに分けてつくれる
Mojoliciousアプリをつくるコツ
• $ mojo generate app MyApp では無く
MyApp::Web みたいなネームスペースで
$ mojo genearate app Uranai::Web
• なぜならUranaiがWeb層だけではないかも
• Uranai::Model / Uranai::Logic
• Uranai::CLI
だいたいこんな構成
./
!"" Uranai/
#   !"" DB/
#   #   %"" Schema.pm
#   !"" DB.pm
#   !"" Model/
#   #   %"" OneModel.pm
#   !"" Web/
#   #   %"" Controller/
#   #   %"" Root.pm
#   %"" Web.pm
%"" Uranai.pm
DBレイヤー / Teng etc.
ビジネスロジック / Perl Module
Webレイヤー /
主にControllerとルーティング
Uranai.pm では パス解決と設定ロードを実装
::Liteからの移行
#!/usr/bin/env perl
use Mojolicious::Lite;
get '/' => sub {
my $self = shift;
$self->stash->{message} = 'Hello Mojo!';
$self->render('index');
};
app->start;
__DATA__
@@ index.html.ep
<html>
<body>
<p><%= $message %></p>
</body>
</html>
lib/Uranai/Web.pm
lib/Uranai/Web/Controller/*
Mojolicious::Controllerを継承
templates/
Mojo::Templateに準ずる
すんなり移行出来る!
占いアプリを実装してみる
実装方針
• 占いのロジック部分をModelとして切り出す
• Uranai::Model::Uranai
• それをControllerから適宜呼び出す
• ViewはMojolicious::Liteと同じものを
別ファイルにする
Uranai::Model::Uranai
package Uranai::Model::Uranai;
use Mouse;
has 'list' => (
is => 'ro',
isa => 'ArrayRef[Str]',
default => sub {
[qw/良い事が起こるでしょう
出会いがあるかも?
不幸になります
/]}
);
sub uranau {
my ($self, $name) = @_;
my $num = 0;
$num += ord ($_) for split //, $name;
my $index = $num % scalar @{$self->list()};
return $self->list->[$index];
}
__PACKAGE__->meta->make_immutable();
ModelはCLIからも使える
use Uranai::Model::Uranai;
use feature 'say';
my $uranai = Uranai::Model::Uranai->new;
say $uranai->uranau('yusukebe');
ってことは単体でテスト出来る!
Uranai::Web::Controller::Root
package Uranai::Web::Controller::Root;
use Mojo::Base 'Mojolicious::Controller';
use Uranai::Model::Uranai;
sub index {
my $self = shift;
$self->render('index');
}
sub result {
my $self = shift;
my $name = $self->req->param('name');
return $self->redirect_to('/') unless $name;
my $uranai = Uranai::Model::Uranai->new;
my $message = $uranai->uranau($name);
$self->stash->{message} = $message;
$self->render('result');
}
1;
アプリをつくるフロー
• Modelを書く
• (テストする)
• Routing / Controller を書く
• テンプレートを書く
• テストサーバを起動しておく
• ブラウザで確認
• 繰り返し...
5. CPANモジュールとの組み合わせと工夫
Mojoliciousだけでは足りない
• CPANモジュールを組み合わせてつくる
• Like LEGO blocks
Mojolicious
OR Mapper FormValidator FillInForm etc.
use FormValidator::Lite;
• 入力値の妥当性をチェックする
• 例:メールアドレス、郵便番号、文字数
FormValidator::Lite->load_constraints(qw/Japanese Email/);
my $validator = FormValidator::Lite->new($self->req);
my $res -> $validator->check(
name => [qw/NOT_NULL/],
mail => [qw/EMAIL/],
{ mails => [qw/mail mail_confirm/] } => ['DUPLICATION']
);
if($validator->has_error) {
$self->stash->{messages} = $validator->get_error_messages();
return $self->render;
}
...;
use Teng;
• データベースを操作する OR Mapper
• とってもシンプル
• Controllerからは操作せずModelから触る
my $teng = Teng::Schema::Loader->load(
dbh => $dbh,
namespace => 'Uranai::DB',
);
my $row = $teng->insert(result => {
text => 'あなたは結婚出来ません',
});
DBを用いた占いアプリの応用
• 固定だった3つの選択肢を増やせるようにしたい
ちなみにやったこと
CREATE TABLE result (
id INT UNSIGNED AUTO_INCREMENT,
text VARCHAR(200) NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (`id`)
);
1. DBをつくって...
lib/Uranai/DB.pm
lib/Uranai/DB/Schema.pm
2. TengのDB/スキーマつくって...
lib/Uranai/Model/Uranai.pm
3. Modelから操作するようにして...
lib/Uranai/Controller/Root.pm
5. Controllerから呼び出す
templates/root/index.html.ep
6. Viewの変更
t/01_model.t
4. 簡単なテスト書いて...
できた!
その他実践で使ってるモジュール
• HTML::FillInForm::Lite
• Data::Validator
• Mouse
• SQL::Maker
• Carton
• Devel::NYTProf / Devel::KYTProf
• Test::mysqld
• Harriet
• Cinnamon
• Server::Starter
• Starman / Starlet
• etc....
Thanks to great Module Authors !
Tips
DELETE/PUTを擬似的にサポートさせる
$self->hook(
before_dispatch => sub {
my $c = shift;
if($c->req->method eq 'POST' && $c->req->param('_method')) {
my $methods = [qw/GET POST PUT DELETE/];
if ( grep { $_ eq uc $c->req->param('_method') } @$methods ) {
$c->req->method( $c->req->param('_method') );
}
}
}
);
my $r = $self->routes;
$r->delete('/entry/:id')->
to( controller => 'Entry', action => 'delete' );
helperでFillInFormをお手軽に
# Uranai::Web
$self->helper(
render_fill => sub {
my ($self, $name) = @_;
my $html = $self->render(template => $name, partial => 1);
return $self->render(
text => HTML::FillInForm::Lite->fill($html, $self->req->params),
format => 'html'
);
}
);
# Uranai::Web::Controller::*
sub form {
my $self = shift;
...;
if ($validator->error) {
$self->stash->{messages} = $validator->get_error_messages();
return $self->render_fill('form');
}
}
Uranai->config();
# Uranai
sub config {
my $mode = $ENV{PLACK_ENV} || 'development';
my $fname = File::Spec->catfile( Uranai->base_dir() , $mode . '.pl' );
my $config = undef;
if( -f $fname ){
$config = do $fname or die "Cannnot load configuration file: $fname";
}else{
die "Cannot find configuration file: $fname";
}
return $config;
}
Mojoliciousのセッションについて
• デフォルトはクライアントにクッキーとして保持
• 気に入らない場合は...
• Plack::Middleware::Session
• 自作セッション管理
6. 今後へ
紹介したこと
• WAFとMVC的な構成について
• 役割を適切に分けることでスッキリと
• 対応するMojoliciousを使った実装
• 占いアプリを例にあげた
Webアプリをつくるには?
• 例えば今回の占いアプリ
• Mojolicious::Liteで実装してみる
• 出来た!俺ってばスゲー体験 その1
• Mojoliciousアプリで拡張してみる
• 出来た!俺ってばスゲー体験 その2
• Tengを学習し占いのパターンを増やす
• 出来た!俺ってばスゲー体験 その3
俺ってばスゲー体験
少しずつ"動いて" "分かる"
扱わなかった主な点
• よりツッコんだMojoliciousの使い方
• テスト => Test::More / Test::Mojo
• 生に近いDB/SQL操作
• その他ミドルウェア => memcached / Redis
• デプロイ => サーバ構成 / アプリサーバ
• パフォーマンス計測、チューニング
• Perl以外のこと => JavaScript / CSS / HTML
まとめ
Mojoliciousではじめよう
• 薄いWAFゆえ理解しやすい
• Mojolicious::Lite のように少しずつつくれる
• 他のWAFにも応用出来る
徐々に学べるフレームワーク
キーワードは...
分けることで分かる
少しずつやろう
終わり
ご質問は個別に!

Mojoliciousでつくる! Webアプリ入門