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.6k
頼りがいのあるORM「Aniki」徹底解説!
YAPC::Hokkaido 2016 Sapporo
karupanerura
December 10, 2016
Tweet
Share
More Decks by karupanerura
See All by karupanerura
GoでParserを書く
karupanerura
5
730
マイ隙間家具OSSたちのご紹介
karupanerura
2
220
1つの言語を原点に色々な言語を学ぶということ
karupanerura
4
2.7k
Javaの実装をPerlでテストする
karupanerura
0
980
Go Generics Overview
karupanerura
0
1.2k
Japan.pm 2021 開催まで至る道
karupanerura
0
1.9k
Perl MongersのためのAWS_CDK入門
karupanerura
0
88
Webシステムのパフォーマンス・チューニング
karupanerura
2
760
WebシステムのパフォーマンスとGo
karupanerura
3
1.6k
Other Decks in Programming
See All in Programming
CSC509 Lecture 02
javiergs
PRO
0
160
型付きで行うVSCode拡張機能開発 / VSCode Meetup #31
mazrean
0
230
M5Stackボードの選び方
tanakamasayuki
0
200
2024-10-02 dev2next - Application Observability like you've never heard before
jonatan_ivanov
0
160
CDKを活用した 大規模コンテナ移行 プロジェクトの紹介
yoyoyopg
0
240
Kubernetes上でOracle_Databaseの運用を楽にするOraOperatorの紹介
nnaka2992
0
150
"noncopyable types" の使いどころについて考えてみた
andpad
0
130
Unlocking Python's Core Magic
leew
0
120
NEWTにおけるiOS18対応の進め方
ryu1sazae
0
190
ECS向けのドリフト検知機構を実装してみた
tkikuc
0
270
Frontend Magic mit CSS Houdini
joergneumann
0
420
Quarto Clean Theme
nicetak
0
220
Featured
See All Featured
Writing Fast Ruby
sferik
625
60k
Mobile First: as difficult as doing things right
swwweet
222
8.8k
From Idea to $5000 a Month in 5 Months
shpigford
380
46k
How GitHub Uses GitHub to Build GitHub
holman
473
290k
[RailsConf 2023] Rails as a piece of cake
palkan
49
4.7k
The Power of CSS Pseudo Elements
geoffreycrofte
71
5.3k
Creatively Recalculating Your Daily Design Routine
revolveconf
217
12k
Documentation Writing (for coders)
carmenintech
65
4.3k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
1
250
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
23
1.7k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
131
32k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
29
1.7k
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で迷っていて帯に短したすきに 長しと感じていた人にはピッタリでしょう • ぜひ試してみてください
ありがとうございました