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
800
マイ隙間家具OSSたちのご紹介
karupanerura
2
230
Mustache Template 話 2024
karupanerura
0
3
1つの言語を原点に色々な言語を学ぶということ
karupanerura
4
2.8k
Javaの実装をPerlでテストする
karupanerura
0
1k
Go Generics Overview
karupanerura
0
1.2k
Japan.pm 2021 開催まで至る道
karupanerura
0
2k
Perl MongersのためのAWS_CDK入門
karupanerura
0
98
Webシステムのパフォーマンス・チューニング
karupanerura
2
790
Other Decks in Programming
See All in Programming
良いユニットテストを書こう
mototakatsu
8
3.1k
Go の GC の不得意な部分を克服したい
taiyow
3
840
nekko cloudにおけるProxmox VE利用事例
irumaru
3
460
Scalaから始めるOpenFeature入門 / Scalaわいわい勉強会 #4
arthur1
1
340
testcontainers のススメ
sgash708
1
130
テストコードのガイドライン 〜作成から運用まで〜
riku929hr
5
950
PSR-15 はあなたのための ものではない? - phpcon2024
myamagishi
0
180
ChatGPT とつくる PHP で OS 実装
memory1994
PRO
2
130
CQRS+ES の力を使って効果を感じる / Feel the effects of using the power of CQRS+ES
seike460
PRO
0
160
return文におけるstd::moveについて
onihusube
1
1.3k
歴史と現在から考えるスケーラブルなソフトウェア開発のプラクティス
i10416
0
140
ドメインイベント増えすぎ問題
h0r15h0
2
430
Featured
See All Featured
The Invisible Side of Design
smashingmag
298
50k
Building a Scalable Design System with Sketch
lauravandoore
460
33k
Agile that works and the tools we love
rasmusluckow
328
21k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
48
2.2k
Automating Front-end Workflow
addyosmani
1366
200k
Making the Leap to Tech Lead
cromwellryan
133
9k
Code Review Best Practice
trishagee
65
17k
Thoughts on Productivity
jonyablonski
68
4.4k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
32
2.7k
Optimising Largest Contentful Paint
csswizardry
33
3k
Fantastic passwords and where to find them - at NoRuKo
philnash
50
2.9k
YesSQL, Process and Tooling at Scale
rocio
170
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で迷っていて帯に短したすきに 長しと感じていた人にはピッタリでしょう • ぜひ試してみてください
ありがとうございました