87
Mojoliciousでつくる! Webアプリ入門 2013/09/21 YAPC::Asia 2013 Yusuke Wada a.k.a. yusukebe Some papix photos are in this slides.Thanks to papix!

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

Embed Size (px)

DESCRIPTION

YAPC::Asia 2013 2013/09/21 Yusuke Wada a.k.a. yusukebe

Citation preview

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

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

2013/09/21YAPC::Asia 2013

Yusuke Wada a.k.a. yusukebe

Some papix photos are in this slides. Thanks to papix!

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

イントロダクション

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

自己紹介

• 和田裕介 1981/12/23 生• 慶應義塾大学制作メディア研究科修了• 株式会社ワディット代表取締役• 株式会社オモロキ取締役兼最高技術責任者• http://yusukebe.com/ or @yusukebe

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

メインワーク

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

問いかけ

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

Perlは大規模Webサービスで使われているが...

初心者がWebアプリに入門する資料が少ない...?

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

あえて去年を振り返る

「新しい」を生み出すためのWebアプリ開発とその周辺

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

何をつくるかは分かった

そのための実装について

本日の主題

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

対象オーディエンス

• Webアプリケーションをつくりたい方• アイデアはあるが実装できない... って方• 最近のWAFについて知りたい方

• Web Application Framework = WAF• Mojoliciousを使った具体的なアプリ構成を知りたい方

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

方針

当たり前に使っている概念、実装、キーワード

噛み砕いて解説

=> GoogleやCPANで検索できるように!

実はすごく周辺からは分かりにくい...?

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

アジェンダ• 例題:占いアプリについて• WebアプリケーションとFramework• Mojoliciousの紹介• Mojolicious::Liteを使う• より実践的なアプリへ• CPANモジュールとの組み合わせと工夫• 今後へ

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

0. 例題:占いアプリについて

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

名前に応じて占い結果を出す• 占い結果はまず3種類• 良い事が起こるでしょう• 出会いがあるかも?• 不幸になります• 入力された名前に対して必ず同じ結果• yusukebe => 「出会いがあるかも?」

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

ハッシュ関数my $name = "yusukebe";my $max = 3;my $number = 0;

# 文字列を一文字ずつ分解for my $char (split //, $name) { # 文字に対応する数値を得る $number += ord $char;}

# 想定される最大値で割った余りを得るmy $result = $number % $max;print "$result\n";

文字列

数値

固定値で割る

余りが結果

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

スクリプトで実現する

ようは診断メ○カーみたいな?

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

1. WebアプリケーションとFramework

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

Webアプリケーションを定義する

Webが誰かと特徴的なインタラクション

をする場合

Webアプリケーション

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

Webのインタラクション

Webアプリケーション

ユーザー例えば自分の名前を

入力する

占いの結果を見る

JSの動作外部のWeb APIミドルウェア etc.

ブラウザプログラムスマホアプリ

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

Web Application Framework• 通称 WAF(ワッフ)• 例えば Ruby on Rails• Webアプリケーションをつくるための土台• MVCモデルに基づく場合が多い

MVCってなんぞー?

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

Model

View

Controller

MVC

MVCである意味は...?

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

MVC以前

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

何がイケてないって?• 色々混じってる• ヘッダー出力• ロジック処理• HTMLの描画• ファイルごとにルーティングする• 重複が起こる

*CGIはデプロイ方法の一つなのでCGI自体が良くないわけではない

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

愚直に.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>";

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

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>

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

冗長になるが見通しがよくなる!

さらにテンプレートをファイルに分けると...

メンテナンス性、再利用性の向上

冗長性はフレームワークが吸収してくれる?

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

とりあえずMVCで考える意味• 混在している固まりを役割で分ける• 理解をすれば冗長になるが開発効率が上がる

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

"分けるとは分かること"

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

切り分け方

1. Router

2. Controller

3. Model / Logic

4. DB層 / OR Mapper

5. View / Static files

ビジネスロジックを扱う

出力や見た目に関する部分

個別のリクエストに基づく処理

データベースにまつわる

URL/METHODに応じて振り分ける

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

役割を分けること =

フレームワーク的思考へ

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

処理の流れ

Router

Controller

Model / Logic

DB / OR Mapper

View

1. リクエスト

2. ディスパッチ

3. モデル呼び出し

4. DB呼び出し 5. DB結果返却

6. 結果

7. レンダリング

8. HTMLなど

9. レスポンス

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

PerlのWAF達• Catalyst• Dancer• Amon2• Kossy• Pickles• Voson• Mojolicious• 自作WAF

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

自作WAFと既存WAF• MVC?を実現するモジュールは揃っている• Plack::Request• Router::Simple• Text::Xslate• ただアプリを書きながらWAFもはキツい• 勉強用に車輪の再発明は多いに歓迎• でもとりあえずWAFを使いましょうね ♥

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

2. Mojoliciousの紹介

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

Mojolicious !• 属に言う「軽量WAF」の一種• 最新Ver.は「4.35」2013年9月12日時点• 作者は「Sebastian Riedel」氏

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

特徴1. VCだけサポート• Routing / Controller• デフォルトではMojo::Templateを使うView• のみサポートし「Model」は対象外

Mojo::Baseその他でクラスつくったりOR Mapper

使ったりして自力で実装してね ♪ っていう...

逆にそれがいいModel/Logic層はWebから切り離すべき

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

特徴2. Not full-stack but full-stackRuby on Rails

DBを扱うまで全てこれ一つで出来る フルスタック

MojoliciousHTTPのRequestやUserAgentまでMojoliciousで使うライブラリが全て自作 フルスタック

コア以外のCPANモジュールに依存しない!

Page 37: Mojoliciousでつくる! Webアプリ入門
Page 38: Mojoliciousでつくる! Webアプリ入門

$ cpanm Mojolicious

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

Mojoliciousなら一発

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

特徴3. その他• アップデートが頻繁

$self->render_json( $ref ); # 4.00 で廃止# => $self->render( json => $ref );

ただし依存が無いのでMojoliciousだけ追っていればよい

• Mojolicious::Lite を使えばファイル一つで• 工夫すればCGIでも動作可能• Perl 5.10.1 以上必須、5.16 以上を推奨• 開発用サーバmorbo、本番用hypnotoad• もちろんPSGI互換

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

小さなアプリから大きなWebアプリまで

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

MojoliciousCPANモジュールの組み合わせ

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

3. Mojolicious::Liteを使う

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

mojoコマンドによるスケルトン

$ mojo generate lite_app myapp.pl

Mojolicious::Lite

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

はじめての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してる

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

立ち上げ方

$ morbo -l "http://*:5000" myapp.pl

morboを使う

$ plackup -p 5000 myapp.pl

plackupで立ち上げる

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

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

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

Hello World から学ぶ

get '/' => sub { my $self = shift; $self->stash->{message} = 'Hello Mojo'; ...;}

テンプレートにデータを渡す

$self->render('index'); # index.html.ep を描画...;<p><%= $message %></p>

テンプレートの記述とレンダー

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

もちろん占いアプリも!

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

Short live coding !?

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

#!/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>

トップページを描画

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

...;

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>

結果を出力

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

Controller内での操作• 全ては「$self」のメソッドを扱うmy $value = $self->req->param('key');

$self->render('entry');

$self->render( json => { key => $value } );

Mojolicious::Controller を継承するのでそのドキュメントを読めばOK!

$self->redirect_to('/');

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

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>

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

Mojolicious::Liteアプリを拡張する

• public/ => 静的ファイルを置く• templates/ => テンプレートファイルを独立

./!"" myapp.pl!"" public/#   !"" css/#   !"" favicon.ico#   !"" images/#   %"" js/%"" templates/ !"" layouts/ #   %"" default.html.ep %"" root/ %"" index.html.ep

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

4. より実践的なアプリへ

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

Mojolicious::Liteだと1ファイルがでかくなるよー

そこで ::Lite じゃないMojoliciousアプリ!

モジュールをいい感じに分けてつくれる

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

Mojoliciousアプリをつくるコツ• $ mojo generate app MyApp では無くMyApp::Web みたいなネームスペースで

$ mojo genearate app Uranai::Web

• なぜならUranaiがWeb層だけではないかも• Uranai::Model / Uranai::Logic• Uranai::CLI

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

だいたいこんな構成./!"" 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 では パス解決と設定ロードを実装

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

::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に準ずる

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

すんなり移行出来る!

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

占いアプリを実装してみる

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

実装方針

• 占いのロジック部分をModelとして切り出す• Uranai::Model::Uranai• それをControllerから適宜呼び出す• ViewはMojolicious::Liteと同じものを別ファイルにする

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

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();

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

ModelはCLIからも使える

use Uranai::Model::Uranai;use feature 'say';

my $uranai = Uranai::Model::Uranai->new;say $uranai->uranau('yusukebe');

ってことは単体でテスト出来る!

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

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;

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

アプリをつくるフロー• Modelを書く• (テストする)• Routing / Controller を書く• テンプレートを書く• テストサーバを起動しておく• ブラウザで確認• 繰り返し...

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

5. CPANモジュールとの組み合わせと工夫

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

Mojoliciousだけでは足りない

• CPANモジュールを組み合わせてつくる• Like LEGO blocks

Mojolicious

OR Mapper FormValidator FillInForm etc.

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

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;}...;

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

use Teng;• データベースを操作する OR Mapper• とってもシンプル• Controllerからは操作せずModelから触る

my $teng = Teng::Schema::Loader->load( dbh => $dbh, namespace => 'Uranai::DB',);

my $row = $teng->insert(result => { text => 'あなたは結婚出来ません',});

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

DBを用いた占いアプリの応用• 固定だった3つの選択肢を増やせるようにしたい

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

ちなみにやったこと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. 簡単なテスト書いて...

できた!

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

その他実践で使ってるモジュール• 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 !

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

Tips

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

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' );

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

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'); }}

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

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;}

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

Mojoliciousのセッションについて

• デフォルトはクライアントにクッキーとして保持• 気に入らない場合は...• Plack::Middleware::Session• 自作セッション管理

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

6. 今後へ

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

紹介したこと

• WAFとMVC的な構成について• 役割を適切に分けることでスッキリと• 対応するMojoliciousを使った実装• 占いアプリを例にあげた

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

Webアプリをつくるには?• 例えば今回の占いアプリ• Mojolicious::Liteで実装してみる• 出来た!俺ってばスゲー体験 その1• Mojoliciousアプリで拡張してみる• 出来た!俺ってばスゲー体験 その2• Tengを学習し占いのパターンを増やす• 出来た!俺ってばスゲー体験 その3

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

俺ってばスゲー体験

少しずつ"動いて" "分かる"

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

扱わなかった主な点• よりツッコんだMojoliciousの使い方• テスト => Test::More / Test::Mojo• 生に近いDB/SQL操作• その他ミドルウェア => memcached / Redis• デプロイ => サーバ構成 / アプリサーバ• パフォーマンス計測、チューニング• Perl以外のこと => JavaScript / CSS / HTML

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

まとめ

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

Mojoliciousではじめよう

• 薄いWAFゆえ理解しやすい• Mojolicious::Lite のように少しずつつくれる• 他のWAFにも応用出来る

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

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

キーワードは...

分けることで分かる

少しずつやろう

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

終わり

ご質問は個別に!