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
7k
頼りがいのあるORM「Aniki」徹底解説!
YAPC::Hokkaido 2016 Sapporo
karupanerura
December 10, 2016
Tweet
Share
More Decks by karupanerura
See All by karupanerura
log
karupanerura
1
78
Use Perl as Better Shell Script
karupanerura
0
800
GoでParserを書く
karupanerura
4
970
マイ隙間家具OSSたちのご紹介
karupanerura
2
270
Mustache Template 話 2024
karupanerura
0
39
1つの言語を原点に色々な言語を学ぶということ
karupanerura
4
2.9k
Javaの実装をPerlでテストする
karupanerura
0
1.3k
Go Generics Overview
karupanerura
0
1.3k
Japan.pm 2021 開催まで至る道
karupanerura
0
2.1k
Other Decks in Programming
See All in Programming
複数チーム並行開発下でのコード移行アプローチ ~手動 Codemod から「生成AI 活用」への進化
andpad
0
180
「AWS CDK入門」の前日譚/Prequelto-Introduction-To-AWSCDK
tyumugi1113
0
100
TypeScript 5.9で使えるようになった import defer でパフォーマンス最適化を実現する
bicstone
1
380
高単価案件で働くための心構え
nullnull
0
160
TypeScriptで設計する 堅牢さとUXを両立した非同期ワークフローの実現
moeka__c
5
2.1k
詳細の決定を遅らせつつ実装を早くする
shimabox
2
1.3k
Chart.jsで長い項目を表示するときのハマりどころ
yumechi
0
150
「正規表現をつくる」をつくる / make "make regex"
makenowjust
1
750
r2-image-worker
yusukebe
1
180
30分でDoctrineの仕組みと使い方を完全にマスターする / phpconkagawa 2025 Doctrine
ttskch
2
430
CloudNative Days Winter 2025: 一週間で作る低レイヤコンテナランタイム
ternbusty
7
1.7k
Honoを技術選定したAI要件定義プラットフォームAcsimでの意思決定
codenote
0
260
Featured
See All Featured
Faster Mobile Websites
deanohume
310
31k
BBQ
matthewcrist
89
9.9k
How To Stay Up To Date on Web Technology
chriscoyier
791
250k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
10
680
How GitHub (no longer) Works
holman
315
140k
Building Adaptive Systems
keathley
44
2.8k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
118
20k
GraphQLとの向き合い方2022年版
quramy
49
14k
Building an army of robots
kneath
306
46k
Rails Girls Zürich Keynote
gr2m
95
14k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
1.8k
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で迷っていて帯に短したすきに 長しと感じていた人にはピッタリでしょう • ぜひ試してみてください
ありがとうございました