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

Preview:

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にも応用出来る

徐々に学べるフレームワーク

キーワードは...

分けることで分かる

少しずつやろう

終わり

ご質問は個別に!

Recommended