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.9k
頼りがいのあるORM「Aniki」徹底解説!
YAPC::Hokkaido 2016 Sapporo
karupanerura
December 10, 2016
Tweet
Share
More Decks by karupanerura
See All by karupanerura
Use Perl as Better Shell Script
karupanerura
0
700
GoでParserを書く
karupanerura
4
910
マイ隙間家具OSSたちのご紹介
karupanerura
2
260
Mustache Template 話 2024
karupanerura
0
26
1つの言語を原点に色々な言語を学ぶということ
karupanerura
4
2.9k
Javaの実装をPerlでテストする
karupanerura
0
1.2k
Go Generics Overview
karupanerura
0
1.3k
Japan.pm 2021 開催まで至る道
karupanerura
0
2.1k
Perl MongersのためのAWS_CDK入門
karupanerura
0
140
Other Decks in Programming
See All in Programming
たった 1 枚の PHP ファイルで実装する MCP サーバ / MCP Server with Vanilla PHP
okashoi
1
200
Deep Dive into ~/.claude/projects
hiragram
9
1.6k
データの民主化を支える、透明性のあるデータ利活用への挑戦 2025-06-25 Database Engineering Meetup#7
y_ken
0
320
Bytecode Manipulation 으로 생산성 높이기
bigstark
2
380
Hypervel - A Coroutine Framework for Laravel Artisans
albertcht
1
110
High-Level Programming Languages in AI Era -Human Thought and Mind-
hayat01sh1da
PRO
0
540
エラーって何種類あるの?
kajitack
5
310
Create a website using Spatial Web
akkeylab
0
310
『自分のデータだけ見せたい!』を叶える──Laravel × Casbin で複雑権限をスッキリ解きほぐす 25 分
akitotsukahara
1
570
今ならAmazon ECSのサービス間通信をどう選ぶか / Selection of ECS Interservice Communication 2025
tkikuc
20
3.7k
都市をデータで見るってこういうこと PLATEAU属性情報入門
nokonoko1203
1
570
20250628_非エンジニアがバイブコーディングしてみた
ponponmikankan
0
470
Featured
See All Featured
Bootstrapping a Software Product
garrettdimon
PRO
307
110k
Unsuck your backbone
ammeep
671
58k
Faster Mobile Websites
deanohume
307
31k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
35
2.4k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
331
22k
Why You Should Never Use an ORM
jnunemaker
PRO
57
9.4k
Side Projects
sachag
455
42k
Reflections from 52 weeks, 52 projects
jeffersonlam
351
20k
Fantastic passwords and where to find them - at NoRuKo
philnash
51
3.3k
Optimizing for Happiness
mojombo
379
70k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
48
5.4k
Optimising Largest Contentful Paint
csswizardry
37
3.3k
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で迷っていて帯に短したすきに 長しと感じていた人にはピッタリでしょう • ぜひ試してみてください
ありがとうございました