Upload
yusuke-wada
View
35
Download
5
Embed Size (px)
DESCRIPTION
YAPC::Asia 2013 2013/09/21 Yusuke Wada a.k.a. yusukebe
Citation preview
Mojoliciousでつくる!Webアプリ入門
2013/09/21YAPC::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 "$result\n";
文字列
数値
固定値で割る
余りが結果
スクリプトで実現する
ようは診断メ○カーみたいな?
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-stackRuby on Rails
DBを扱うまで全てこれ一つで出来る フルスタック
MojoliciousHTTPの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
MojoliciousCPANモジュールの組み合わせ
3. Mojolicious::Liteを使う
mojoコマンドによるスケルトン
$ mojo generate lite_app myapp.pl
Mojolicious::Lite
はじめてのMojolicious::Lite#!/usr/bin/env perluse 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 perluse 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 perluse 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::Uranaipackage 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::Rootpackage 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.pmlib/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にも応用出来る
徐々に学べるフレームワーク
キーワードは...
分けることで分かる
少しずつやろう
終わり
ご質問は個別に!