Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
頼りがいのあるORM「Aniki」徹底解説!
Search
karupanerura
December 10, 2016
Programming
6
6.7k
頼りがいのあるORM「Aniki」徹底解説!
YAPC::Hokkaido 2016 Sapporo
karupanerura
December 10, 2016
Tweet
Share
More Decks by karupanerura
See All by karupanerura
GoでParserを書く
karupanerura
5
770
マイ隙間家具OSSたちのご紹介
karupanerura
2
220
1つの言語を原点に色々な言語を学ぶということ
karupanerura
4
2.7k
Javaの実装をPerlでテストする
karupanerura
0
1k
Go Generics Overview
karupanerura
0
1.2k
Japan.pm 2021 開催まで至る道
karupanerura
0
2k
Perl MongersのためのAWS_CDK入門
karupanerura
0
90
Webシステムのパフォーマンス・チューニング
karupanerura
2
780
WebシステムのパフォーマンスとGo
karupanerura
3
1.6k
Other Decks in Programming
See All in Programming
型付き API リクエストを実現するいくつかの手法とその選択 / Typed API Request
euxn23
8
2.2k
Macとオーディオ再生 2024/11/02
yusukeito
0
370
What’s New in Compose Multiplatform - A Live Tour (droidcon London 2024)
zsmb
1
470
Amazon Bedrock Agentsを用いてアプリ開発してみた!
har1101
0
330
광고 소재 심사 과정에 AI를 도입하여 광고 서비스 생산성 향상시키기
kakao
PRO
0
170
聞き手から登壇者へ: RubyKaigi2024 LTでの初挑戦が 教えてくれた、可能性の星
mikik0
1
130
Contemporary Test Cases
maaretp
0
130
Better Code Design in PHP
afilina
PRO
0
120
「今のプロジェクトいろいろ大変なんですよ、app/services とかもあって……」/After Kaigi on Rails 2024 LT Night
junk0612
5
2.1k
イベント駆動で成長して委員会
happymana
1
320
リアーキテクチャxDDD 1年間の取り組みと進化
hsawaji
1
220
Tauriでネイティブアプリを作りたい
tsucchinoko
0
370
Featured
See All Featured
Happy Clients
brianwarren
98
6.7k
Typedesign – Prime Four
hannesfritz
40
2.4k
Become a Pro
speakerdeck
PRO
25
5k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
28
9.1k
The Language of Interfaces
destraynor
154
24k
Into the Great Unknown - MozCon
thekraken
32
1.5k
Building Better People: How to give real-time feedback that sticks.
wjessup
364
19k
A designer walks into a library…
pauljervisheath
203
24k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
126
18k
Fireside Chat
paigeccino
34
3k
Stop Working from a Prison Cell
hatefulcrawdad
267
20k
KATA
mclloyd
29
14k
Transcript
頼りがいのある O/R Mapper Aniki徹底解説 id:karupanerura YAPC::Hokkaido 2016
悪天候により お足元の悪い中 ご足労頂きまして 本当にありがとうございます
ちょっとしたおことわり • 時間の関係上、駆け足での紹介となります • 資料は公開するので安心してください • 質問はリアルタイムに発言してください • 温かみのあるdisも歓迎します •
細かいことは懇親会とかで話しましょう
$ prove -v about-me.t • 1..8 • ok 1 -
id: karupanerura • ok 2 - PAUSE ID: KARUPA • ok 3 - lang: Perl5/XS/Go/C/etc.. • ok 4 - love: ♨
$ prove -v about-me.t • ok 5 - Japan Perl
Association: 理事 • ok 6 - DeNA: Engineer (2016/09-) • ok 7 - Mackerel UG: Organizer • ok 8 - Gotanda.pm: Organizer • All tests successful.
JPA x Kansai.pm http://yapcjapan.org/kansai2017/ YAPC::Kansai 2017 OSAKA
突然ですが質問です
O/R Mapper について • 使ったことがある(目的問わず) • 知っているがあえて使わない • よく知らないけど興味はある •
ぜんぜん興味ないです
Perlの O/R Mapper について • 特に不満なく使っている • 不満があり渋々使っている • PerlではDBIで十分
ありがとうございます!
ぼくの場合
ぼくのO/R Mapper事情 • WebアプリをPerlで開発 (at 前職) • ORMとして主にTengを利用 • 基本的に必要十分な機能があり快適
• 仕様が大きくテーブルの依存関係が複雑 • リレーションシップサポートが欲しくなる
Teng魔改造時代 • Tengにリレーションシップサポートを実装 • 無理やりRowクラスにリレーションシップ 情報を持たせて参照する • めちゃくちゃ複雑になる • DDLとSchema.pmと三重管理化
• つらい
DBICについて考える • DBICにはリレーションシップサポートがある • やっぱり重い • ソースが巨大で問題を追うのがつらそう • DBIx::Class •
ほか、CPANを漁るが良さそうなのがない
こまかい不満とか • JOIN句が作られるとちょっと嫌 • MySQLのindexの使われ方が予想しにくい • Schema.pmとDDLの二重管理が嫌 • 外部キー制約とかからよしなにやってくれ •
しかし外部キー制約を外したいケースも
しゃーない、作るか!
Aniki 爆 誕
こちらが経緯となります
ほんじつのあじぇんだ • コンセプトと設計 • 基本的な使い方 • 高度な使い方 • 今後の展望 •
まとめ
コンセプトと設計
構想 • 基本的にTengを踏襲 • なるべく複雑なことはしない • シンプルなものを組み合わせて作る • 独自な実装はなるべく避ける •
なるべく状態を持たない
“Do it simple stupid!”
難しいことをしない • DBIx::Handler (DBIx::TransactionManager) • DBIx::Schema::DSL (SQL::Translator) • SQL::Maker (SQL::QueryMaker)
• SQL::NamedPlaceholder これらのモジュールへの委譲で実現
驚き最小の法則 • 副作用は明示的に記述する • e.g.) insert • e.g.) insert_and_fetch_id •
e.g.) insert_and_fetch_row 勝手に余計な副作用を作らない
“Don’t repeat your self”
必要十分の抽象化レイヤ • Aniki::Handler 接続管理 • Aniki::Reuslt::Collection クエリ結果の集合 • Aniki::Row クエリ結果の行
• Aniki::QueryBuilder クエリビルダ これら全てを(独自)拡張クラスに置換可能
プラグイン機構 • Mouse::RoleによるTraitにより実現 • プラグインが作りやすくなっている • Mouseは高速でオーバーヘッドも少ない • RoleなのでCollectionやRowにも適用可能 •
コアでもCollection#pagerはRoleで実装
“YAGNI”
必要になるまで実装しない • リレーションシップサポート • prefetchせずともアクセサは使えるように • Rowクラス • 各カラムへのアクセサをメソッドで提供 •
メソッドの上書きも容易
結果
モジュールの関係性 Aniki 委譲 → Aniki::Filter 委譲 → Aniki::Handler (DBIx::Handler) 委譲
→ Aniki::QueryBuilder (SQL::Maker) 委譲 → Aniki::Schema (SQL::Translator) 結果 → Aniki::Result::Collection 行 → Aniki::Row Aniki::Plugin (Mouse::Role) 拡張 → 各モジュール
コード行数の比較 • Teng: 1908[lines] • Aniki: 2444[lines] • DBIx::Class: 39710[lines]
モジュール数の比較 • Teng: 18[modules] • Aniki: 25[modules] • DBIx::Class: 154[modules]
ベンチマーク (SELECT) • 19% faster than Teng • 148% faster
than DBIx::Class
ベンチマーク (INSERT) • INSERT(and fetch row): • 18% faster than
Teng • 40% faster than DBIx::Class • INSERT(and fetch id): • 21% faster than Teng
ベンチマーク (UPDATE) • UPDATE(from row): • 34% slower than Teng
• 49% faster than DBIx::Class • UPDATE(from where condition): • 5% faster than Teng
ベンチマーク (DELETE) • DELETE(from row): • 2% slower than Teng
• 422% faster than DBIx::Class • DELETE(from where condition): • 5% faster than Teng
小さなコードで 多くの機能を実現
なかなか 高いパフォーマンス
使いたくなってこない? ん?
基本的な使い方
基本的にはTengと同じ • setupメソッドでschemaクラス等を登録 • Anikiを継承してnewしてばっちこい • ほとんどのインターフェースに互換性がある • Tengからの移行はとてもかんたん •
一部違うインターフェースもあるがほぼ同じ
Anikiのはじめかた • install-anikiでAnikiを継承したクラスを生成 • setupメソッドなどから不要なものを消す • つかう
install-aniki % install-aniki --lib=./lib MyApp::DB Creating MyApp::DB ... done lib/MyApp/DB.pm
syntax OK Creating MyApp::DB::Schema ... done lib/MyApp/DB/Schema.pm syntax OK Creating MyApp::DB::Filter ... done lib/MyApp/DB/Filter.pm syntax OK Creating MyApp::DB::Result ... done lib/MyApp/DB/Result.pm syntax OK Creating MyApp::DB::Row ... done lib/MyApp/DB/Row.pm syntax OK
create table % mysqladmin -uroot create myapp % perl -Ilib
-MMyApp::DB::Schema -e 'print MyApp::DB::Schema->output' | mysql -uroot myapp
Example: setup package MyApp::DB; use Mouse; extends qw/Aniki/; __PACKAGE__->setup( schema
=> 'MyApp::DB::Schema', # required filter => 'MyApp::DB::Filter', # optional result => 'MyApp::DB::Result', # optional row => 'MyApp::DB::Row', # optional );
Example: schema (1) package MyApp::DB::Schema; use DBIx::Schema::DSL; database 'MySQL'; #
required create_table 'post' => columns { integer 'id', primary_key, auto_increment; varchar 'subject', size => 255; text 'body'; datetime 'created_at'; datetime 'updated_at'; };
Example: schema (2) package MyApp::DB::Schema; create_table 'comment' => columns {
integer 'id', primary_key, auto_increment; integer 'post_id'; varchar 'name', size => 255; text 'body'; datetime 'created_at'; datetime 'updated_at'; belongs_to 'post'; };
Example: filter (optional) package MyApp::DB::Filter; use Aniki::Filter::Declare; inflate qr/_at$/ =>
sub { my $datetime = shift; return Time::Moment->from_string($datetime.'Z', lenient => 1); }; deflate qr/_at$/ => sub { my $datetime = shift; return $datetime->at_utc->strftime('%F %T') if blessed $datetime and $datetime->isa('Time::Moment'); return $datetime; };
Example: filter (optional) package MyApp::DB::Filter; trigger insert => sub {
my ($row, $next) = @_; $row->{created_at} = Time::Moment->now; return $next->($row); }; trigger update => sub { my ($row, $next) = @_; $row->{updated_at} = Time::Moment->now; return $next->($row); };
Example: connect to database use MyApp::DB; my $connect_info = [
'dbi:mysql:dbname=myapp', 'root', '', ]; my $db = MyApp::DB->new( connect_info => $connect_info, );
Example: select my $row = $db->select(post => { id =>
1, }, { limit => 1 })->first; my @rows = $db->select(post => {})->all;
Example: relationship my $row = $db->select(post => { id =>
1, }, { limit => 1, prefetch => [qw/comment/], })->first; my @rows = $db->select(post => { id => { between => [1, 10] }, }, { prefetch => [qw/comment/], })->all;
Example: row access my $post = $db->select(post => { id
=> 1 }); # post.subject my $subject = $post->subject; # resolve relationship # => SELECT * FROM comments WHERE post_id = 1 my @comments = $post->comments; # You can pre-fetch with select option # But, It's optional. Not required.
Example: insert $db->insert(post => { subject => 'YAPC::Hokkaido 2016 Sapporo',
body => '北海道だよ', }); my $id = $db->insert_and_fetch_id(post => { ..., }); my $row = $db->insert_and_fetch_row(post => { ..., });
Example: update my $updated_rows_count = $db->update(post => { subject =>
'YAPC::Kansai 2017 Osaka', body => '大阪だよ', }, { id => 1, }); $db->update($row => { body => '新大阪だよ', });
Example: delete my $deleted_rows_count = $db->delete(post => { id =>
1, }); $db->delete($row);
Example: transaction $db->txn(sub { my $row = $db->select(post => {
id => 1 }, { for_update => 1 }); my ($subject, $res) # e.g.) "ぬるぽ (1)" = ($row->subject =~ /^(.+) \((\d+)\)$/); $res++; $db->update($row => { subject => $subject." ($res)", }); $db->insert(comments => { body => "けっこう欠航でてる", }); });
DEMO
高度な使い方
プラグイン
AnikiではPluginが使える • 実態はMouse::RoleなのでwithすればOK • Anikiのコアでもいくつかパッケージを提供 • Aniki::Plugin::Count • Aniki::Plugin::Pager •
Aniki::Plugin::SearchJoined などなど
Pluginを作るには • Mouse::Roleを使う • Mouse DSLのwithでapplyする • 以上 • プリインのプラグインの真似するといいです
例) Aniki::Plugin::Count • countメソッドを提供(定義するだけでOK) • requresで依存するメソッドを列挙 • 関係ないクラスに間違ってapplyしてハマ るのを防ぐ •
withで適用するとcountメソッドが呼べるよ うになる
拡張
Anikiのクラスは拡張可能 • handler/row/result/sql_builderのクラスを 独自のクラスに差し替えることが可能 • row/resultクラスは全体で共通となるクラス にも、テーブル毎に違うクラスにもできる • 一部だけ特別扱いとかも可能 •
今回はよく使うであろうRowクラスを例に
Rowクラスの拡張 • rowクラスにMyApp::DB::Rowを指定してい た場合 • MyApp::DB::Row::${TableName}が存在す ればそれにblessされる • たとえばpostテーブルなら MyApp::DB::Row::Postを拡張すればよい
どんなときにRowを拡張するか • カラム名は変えたいけどアプリのコードの変 更は最小限にしたいとき • データの一部だけをカラムのように参照した いとき • 単行のMD5ハッシュ値などを算出したいとき •
要するに、苦しいとき、悲しいとき
リレーションシップ
リレーションシップサポート • テーブル間の関係性をもとにDB操作をサポー トするもの • あるテーブルへのSELECTの結果をもとに他 のテーブルのデータもSELECTしたい(JOIN したい)ケースはよくある • オブジェクトとして自然と扱えるとうれしい
Tengの場合 • Rowくらすにアクセサを生やす • 愚直にやると簡単にN+1問題を生む • 自前でJOINなり何なりをする必要がある • 同じようなコードを毎度書くのはだるい
DBICの場合 • Schemaクラスに関係を定義 • 一覧できない • リレーションシップサポート自体は優秀 • 高い完成度
Anikiの場合 • DBIx::Schema::DSLの外部キー定義情報を利 用 • belongs_to 'user'; # $row->user •
逆向きの関係性も自動判別 • 外部キー制約とは別の定義も可能 • Aniki::Schema::Relationship::Declare
どういうことなのか 時間があればここで サンプルコードを 見ながら解説
マイグレーション
Anego • id:papix作のSQL::Translatorベースのマイ グレーションツール • Anikiで利用するDBIx::Schema::DSLも SQL::Translatorベースなので相性が良い • 手軽で便利っぽい
GitDDL(::Migrator) • DDLはファイルとして存在する必要がある • これもGitと同期するなら使いやすそう
ごめんなさい このへんはまだ調査不足 でもポテンシャルはあるよ
今後の展望
よくある質問 • ActiveRecordっぽく使いたい • ResultSetほしい つらいとおもうけど まあ、わからなくもない
機能追加 • Lint機能 • Aniki::Result::Collectionからprefetch • より便利なdeflate • ResultSet/Iteratorの追加 •
etc..
コントリビューター募集 • ぼくの時間も有限 • Anikiに機能追加したい人を求めてます • pull-reqなげてくれればレビューします • issueを立ててくれてもよいです •
せめて一度ためしてみてください
·ͱΊ
まとめ • Anikiはシンプルかつ高機能で、ある程度のハ イパフォーマンスを実現しており、たよりが いがあります • TengとDBICで迷っていて帯に短したすきに 長しと感じていた人にはピッタリでしょう • ぜひ試してみてください
ありがとうございました