Upgrade to Pro — share decks privately, control downloads, hide ads and more …

頼りがいのあるORM「Aniki」徹底解説!

karupanerura
December 10, 2016

 頼りがいのあるORM「Aniki」徹底解説!

YAPC::Hokkaido 2016 Sapporo

karupanerura

December 10, 2016
Tweet

More Decks by karupanerura

Other Decks in Programming

Transcript

  1. $ 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: ♨
  2. $ 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.
  3. ぼくのO/R Mapper事情 • WebアプリをPerlで開発 (at 前職) • ORMとして主にTengを利用 • 基本的に必要十分な機能があり快適

    • 仕様が大きくテーブルの依存関係が複雑 • リレーションシップサポートが欲しくなる
  4. 必要十分の抽象化レイヤ • Aniki::Handler 接続管理 • Aniki::Reuslt::Collection クエリ結果の集合 • Aniki::Row クエリ結果の行

    • Aniki::QueryBuilder クエリビルダ これら全てを(独自)拡張クラスに置換可能
  5. モジュールの関係性 Aniki 委譲 → Aniki::Filter 委譲 → Aniki::Handler (DBIx::Handler) 委譲

    → Aniki::QueryBuilder (SQL::Maker) 委譲 → Aniki::Schema (SQL::Translator) 結果 → Aniki::Result::Collection 行 → Aniki::Row Aniki::Plugin (Mouse::Role) 拡張 → 各モジュール
  6. ベンチマーク (INSERT) • INSERT(and fetch row): • 18% faster than

    Teng • 40% faster than DBIx::Class • INSERT(and fetch id): • 21% faster than Teng
  7. ベンチマーク (UPDATE) • UPDATE(from row): • 34% slower than Teng

    • 49% faster than DBIx::Class • UPDATE(from where condition): • 5% faster than Teng
  8. ベンチマーク (DELETE) • DELETE(from row): • 2% slower than Teng

    • 422% faster than DBIx::Class • DELETE(from where condition): • 5% faster than Teng
  9. 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
  10. create table % mysqladmin -uroot create myapp % perl -Ilib

    -MMyApp::DB::Schema -e 'print MyApp::DB::Schema->output' | mysql -uroot myapp
  11. 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 );
  12. 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'; };
  13. 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'; };
  14. 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; };
  15. 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); };
  16. Example: connect to database use MyApp::DB; my $connect_info = [

    'dbi:mysql:dbname=myapp', 'root', '', ]; my $db = MyApp::DB->new( connect_info => $connect_info, );
  17. Example: select my $row = $db->select(post => { id =>

    1, }, { limit => 1 })->first; my @rows = $db->select(post => {})->all;
  18. 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;
  19. 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.
  20. 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 => { ..., });
  21. Example: update my $updated_rows_count = $db->update(post => { subject =>

    'YAPC::Kansai 2017 Osaka', body => '大阪だよ', }, { id => 1, }); $db->update($row => { body => '新大阪だよ', });
  22. 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 => "けっこう欠航でてる", }); });
  23. Anikiの場合 • DBIx::Schema::DSLの外部キー定義情報を利 用 • belongs_to 'user'; # $row->user •

    逆向きの関係性も自動判別 • 外部キー制約とは別の定義も可能 • Aniki::Schema::Relationship::Declare