アプリを書くときみんなModelはどうしてるの? って話

9480e27b8d6ae2a668a98144516b5513?s=47 acidlemon
December 16, 2015

アプリを書くときみんなModelはどうしてるの? って話

Gotanda.pm #7 vs Yokohama.pm #13 (2015/12/16)のスライドです

9480e27b8d6ae2a668a98144516b5513?s=128

acidlemon

December 16, 2015
Tweet

Transcript

  1. ΞϓϦΛॻ͘ͱ͖ ΈΜͳ.PEFM͸ Ͳ͏ͯ͠Δͷ ͬͯ࿩ BDJEMFNPO  (PUBOEBQNWT:PLPIBNBQN

  2. ޒ൓ాͷΈͳ͞Μ͜Μ͹Μ͸ w ԣ඿ͷձ͔ࣾΒ΍͖ͬͯ·ͨ͠ ॅ·͍͸ח૔Ͱ͢  w 1FSM(PMBOH$ ϓϩάϥϚʔ كʹ3VCZͱ+BWBTDSJQU 

    w 8FBSFIJSJOHTFFXXXLBZBDDPNSFDSVJU !BDJEMFNPO .BTBUPTIJ,BXB[PF 4PGUXBSF&OHJOFFS BU,":"$*OD
  3. ۙگ w ࡢ೥ޙ൒͔Β೥ͪΐͬͱ͘Β͍ͣͬͱ(PΛॻ ͍ͯͨͷͰ͕͢ɺ࠷ۙ1FSMʹؼ͖ͬͯ·ͨ͠ w ٱʑʹ1FSMͰ8FCαʔϏεΛॻ͍͍ͯ·͢ w ͳΜ͔ͩΜ͍ͩͬͯखʹͳ͡ΜͰͯྑ͍

  4. None
  5. "OJLJͷ࿩͸͠·ͤΜ ઌ೔ͬ͟ͱιʔε͸ோΊͨͷͰ ࠓ೔ͷ࿩ฉ͍͔ͯΒຊؾग़͢༧ఆ

  6. ΧϠοΫͷ8FCαʔϏε w 1FSMͩͱ͜Ε·Ͱ͜Μͳߏ੒Ͱͨ͠ w 8"'͸"SL $BUBMZTUMJLF QPXFSFECZ  w 03.͸%#*Y$MBTT


    Ұ෦ྫ֎༗Γʜ-PCJ͸5FOHͰ͢  w 0014ZTUFN͸.PVTF w 0CKFDU$POUBJOFSʹͳΜ͔৭ʑԡ͠ࠐΜͰΔ
 ͨͱ͑͹%#3FEJT઀ଓͷӬଓԽͳͲ
  7. ৽͘͠ຊॻ͘ʹ͋ͨͬͯ શ෦ม͑ͯΈͨ

  8. ͳͥม͔͑ͨ w ٱʑʹҰ͔Βॻ͘ͷͰɺͨ·ʹ͸"NPO  5FOHͱ͔Ͱॻ͍ͯΈΑ͏͔ͳʔͱࢥͬͨ w .PP͸ɺͦͷ࣌࢖͏༧ఆͩͬͨ&NBJM4FOEFS ͕.PPͩͬͨͷͰ࢖ͬͯΈΔ͜ͱʹͨ͠ w ͍ͭ΋ͱҧ͏։ൃϫΫϫΫ

  9. "NPO 5FOH΍ .PPͷ࿩͸͠·ͤΜ

  10. ࠓ೔͸ .PEFMͷ࿩Λ͠·͢

  11. .PEFMͷ࿩ w 1FSMͰ8FCΞϓϦॻ͘ͱ͖ͷهࣄɺ8"'΍ 1MBDLͷ࿩͸Α͘ग़ͯ͘Δ͚Ͳ.PEFMͷ࿩͸͋ ·ΓͰͯ͜ͳ͍ w 8"'͸.7$ͷ7ͱ$ͳͷͰ8"'͸.PEFMͷ ໘౗Λݟ͍ͯͳ͍ w ΈΜͳ.PEFM͸Ͳ͏ͯ͠Δͷ͔ͳ͊ͱࢥͬͯɺ

    ࣗ෼ͷ࿩Λͯ͠ΈΔ͜ͱʹͨ͠
  12. .PEFM03. w .PEFMͱ͸Կ͔ͱ͍͏࿩ʹͳΔͱɺ03.ͷ࿩ ͕ग़ͯ͘Δ ྫ͑͹5FOH3PXʹͨ͘͞Μϩ δοΫΛੜ΍͢࿩ͱ͔͋Γ·͢ΑͶ  w 03.͸͔֬ʹΦϒδΣΫτࢦ޲Ͱ%#Λѻ͑Δ ͷͰศར͚ͩͲɺϏδωεϩδοΫΛ࣮૷͢Δ

    .PEFMͦͷ΋ͷͰ͸ͳ͍ͷͰ͸ ͱࢥ͍ͬͯΔ w ͱ͍͏ͷ΋ετΞઌ͸%#͚ͩͰ͸ͳ͍
  13. ࠓ೔͸͜Μͳ࿩Λ͠·͢ w 8"'͕σΟεύονͨ͠ޙͷॲཧΛࣗ෼͕Ͳ͏ ϞδϡʔϧΛ෼ׂͯ͠ॻ͍͍ͯΔ͔ w ʮ͜͏͢΂͖ʯͰ͸ͳ͘ʮΦϨ͸͜͏ࢥͬͯ͜͏ ॻ͍͍ͯΔʯͱ͍͏࿩ͳͷͰɺ͍Ζ͍Ζࢥ͏ͱ ͜Ζ͋Δͱࢥ͍·͕͢ɺ͓खݩʹ΁͐ϘλϯΛ ࣋ͬͨؾ࣋ͪͰฉ͍͍͚ͯͨͩΕ͹ͱࢥ͍·͢

  14. શମ૾ $POUSPMMFS 4FSWJDF .PEFM %#3FEJTFUDʜ .PEFMΦϒδΣΫτΛ࡞ͬͯ ϩδοΫΛॱ൪ʹ࣮ߦ͢Δ 3FRVFTUͳͲͷ7BMJEBUJPOΛͯ͠ 4FSWJDFΛݺͼग़͢ ΦϒδΣΫτࢦ޲ʹج͍ͮͯ

    ࣗ෼ͷৼΔ·͍Λ͢Δ ֎෦*0Λߦͬͯ σʔλͷӬଓԽ΍ΩϟογϡΛߦ͏ ‑ ‑ ‑
  15. $POUSPMMFS‎4FSWJDF w $POUSPMMFSͷ࢓ࣄ w ϦΫΤετύϥϝʔλͱηογϣϯσʔλͷݕূ w ݕূࡁΈͷҾ਺ͷ४උ w ΤϯυϙΠϯτʹରԠ͢Δ4FSWJDFGVODUJPOΛݺͼ ग़͢

    w 4FSWJDFͷ໭Γ஋Λ࢖ͬͯϨεϙϯεͲ͏ʹ͔͢Δ )5.-͔+40/ΛϨϯμϦϯά͢Δ
  16. ΈΜͳ೔ຊޠΑΓ΋ 1FSMͷ΄͏͕ ಘҙͩͱࢥ͏ͷͰɺ 1FSMͰॻ͍͍͖ͯ·͢

  17. $POUSPMMFS‎4FSWJDF # MyApp::Web::Dispatcher.pm base 'MyApp::Controller'; post 'api/game/start' => "Game#start"; #

    MyApp::Controller::Game.pm sub start { my ($class, $c) = @_; # FormValidator::LiteͰݕূ͢Δ(লུ) my $game_id = $c->req->parameter('game_id'); my $player_id = $c->session->get('player_id'); # Service Endpoint΁౤͛Δ my ($result, $err); try { $result = MyApp::Service::Game->start( game_id => $game_id, player_id => $player_id, ); } catch { $err = $_; }; $c->render_json({ error => $err }) if $err; return $c->render_json({ result => $result }); }
  18. 4FSWJDF‎.PEFM w 4FSWJDFͷ࢓ࣄ w ύϥϝʔλΛݩʹ.PEFMΦϒδΣΫτΛੜ੒ w .PEFMΦϒδΣΫτͷϩδοΫΛݺͼग़͢ w ϩδοΫͷ࣮ߦ݁ՌΛجʹɺ"1*࢓༷ʹଈ͠ ͨ໭Γ஋Λ࡞Δ

    ༨ஊੲ͸ࣾ಺ͰʮҰͭͷ"1*&OEQPJOUʹରԠͯ͠Ұͭͷ.PEFMͷ&OUSZ1PJOUΛ༻ҙ͢΂͖͡ΌͶ ʯͬͯ ओுͯͨ͠Μ͚ͩͲɺΈΜͳʮ͑ʔʯͬͯײ͡Ͱߏͬͯ͘Εͳ͔ͬͨͷͰ4FSWJDF͍ͬͯ͏໊લۭؒʹ෼͚ͨ
  19. 4FSWJDF‎.PEFM # MyApp::Service::Game.pm sub start { my ($class, %args) =

    @_; my $game = MyApp::Model::Game->new( id => $args{game_id} ); my $player = MyApp::Model::Player->new( id => $args{player_id} ); # ήʔϜʹ༩͑ΔϥϯμϜύϥϝʔλΛ༻ҙ state $generator = Math::Random::MT->new; my $randval = $gen->rand(); # ήʔϜ։࢝ͩʔ! my $result = $game->start( player => $player, seed => $randval, ); return $result; }
  20. ༨ஊͷଓ͖ w લϖʔδͷྫɺઃܭͷ࢓ํͱͯ͠௨Γ͋Γ·͢ w ࣾ಺ͩͱجຊతʹޙऀʹͳ͍ͬͯΔ͚Ͳɺݸਓతʹ͸޷͖Ͱ͸ແ͍͔ͳʔ w 1MBZFS͕ߦಈͷओମ͔ͩΒͱ͍͏ͷ͕ޙऀʹͳͬͯΔཧ༝ͳΜ͚ͩͲɺ ͦΕΛݴ͍ग़͢ͱηογϣϯඞਢͷ"1*͸શ෦ϓϨΠϠʔ͕ૢ࡞͢ΔͷͰ ͦ͏͍͏ݺͼग़͠ʹͳͬͯ͠·͏ 1MBZFS͕ංେԽ͢Δ

     w ࣮ࡍʹͦͷϓϩδΣΫτͷίʔυΛௐ΂ͯΈͨͱ͜Ζ6TFS ͷύοέʔ δ͕ݸ͘Β͍͋ͬͯ.PEFMͷίʔυͷ͕6TFS ͩͬͨ w ͦ͏͍͏ྫ͸4FSWJDFʹ෼཭͢Ε͹͍͍͔ͳͱࢥͬͯ͜ͷ࿩Λ͍ͯ͠·͢ my $result = $game->start( player => $player, seed => $randval, ); my $result = $player->game->start( game => $game, seed => $randval, ); 1MBZFSΛҾ਺ʹ(BNFΛݺͿ (BNFΛҾ਺ʹ1MBZFS(BNFΛݺͿ
  21. .PEFM‎%#3FEJT w .PEFMͷ࢓ࣄ w ͜͜ʹϏδωεϩδοΫΛॻ͘ʂʂʂ w %#ͱ͔3FEJTͱ͔Λୟ͘ʂʂ w ֎෦"1*΋ୟ͘ʂ w

    .PEFMͷ֎͔ΒԼ૚ ֎෦*0 ΛӅณ͢Δ
  22. .PEFMͷྫɺ۩ମతʹ w 3PXͱͷύοέʔδ w %#ͷ3PXΛIBTͯ͠Ӆณ͢Δύλʔϯ w %#3FEJTΛ࢖ͬͨϩδοΫύοέʔδ w VTFSςʔϒϧΛFNBJMͰݕࡧɺॏෳνΣοΫΈ͍ͨͳέʔε
 %#্Ͳ͔͜ʹैଐ͍ͯ͠Δ

    FNBJMΛओମͱͯ͠ѻ͏ w ֎෦"1*Λ༏͘͠แΈࠐΉύοέʔδ w ՝ۚݕূ"1*΍1VTI௨஌EBFNPOΛୟ͘ͳͲ w ಛఆͷ৚݅Λຬͨ͢σʔλू߹શମΛѻ͏ύοέʔδ w ϥϯΩϯάσʔλͳͲ
  23. .PEFMΛॻ࣌͘͜͏ͯ͠Δ w ΦϒδΣΫτࢦ޲Ͱॻ͘ɻ.PPͳΓ.PVTFͳΓ Λ࢖͏ ΋ͪΖΜ$MBTT"DDFTTPSྨͰ΋͍͍ͱ͓΋͏  w ϑΝΠϧ͕௕͘ͳΒͳ͍Α͏ʹద౓ʹ෼ׂ͢Δ
 υϝΠϯۦಈઃܭͱ͔ࢀߟʹ͢Ε͹͍͍ͱࢥ͏
 ͱҒͦ͏ʹ͍͏ׂʹࣗ෼͸ͦͷखͷจݙͳͲΛશવ·͡ΊʹಡΜͰͳ͍ͷͰɺ

    ΦϨΦϨઃܭཧ࿦Λิڧ͢Δͱ͖͚ͩͦ͏͍͏ͷಡΜͰΔ
  24. .PEFMΛॻ࣌͘͜͏ͯ͠Δ w 8FC΂ͬͨΓʹͳΒͳ͍Α͏ʹॻ͘ɻ
 ͦ͏͢Ε͹$-*ͱ͔͔Β΋ݺͿͱ͖ʹࠔΒͳ͍
 .PEFMͷҾ਺ʹ͸1MBDL3FRVFTU΍8"'ͷ$POUFYUΛ௚઀౉͞ͳ͍ɺ தͷύϥϝʔλΛऔΓग़ͯ͠౉͢  w ؾ࣋ͪͱͯ͠͸8"'Λ͛͢ସ͑ͯ΋.PEFM͸ͦ ͷ··ྲྀ༻Ͱ͖ΔΑ͏ʹ.PEFMΛ࡞Δ


    ͔͠͠ݱঢ়"NPOͳͷͰ.Z"QQDPOUFYU͔ΒECͱ͔SFEJTͷίωΫ γϣϯΦϒδΣΫτΛऔͬͯ͘Δ࡞Γʹͯ͠͠·͏ͱͦ͜ʹґଘ͢Δͱ͍͏ʜ 
 ‎ݱ࣮తʹ8"'ม͑Δͱ͔ͦ͏ͦ͏ͳ͍Αͳ
  25. 3PXͱͷͷྫ # MyApp::Model::Player.pm use Moo; has id => ( is

    => 'ro', ); has _row => ( is => 'lazy', default => sub { my $self = shift; my $db = MyApp->context->db; # Teng my $row = $db->single('player', { id => $self->id }); return $row; }; ); sub userinfo { my $self = shift; my $hash = $self->_row->get_columns; delete $hash->{api_token}; # ΈΜͳʹ͸φΠγϣͩΑ return $hash; }
  26. 3PXͱͷͷྫ ଓ͖ # MyApp::Model::Player.pm (ଓ͖) # ৽نϓϨΠϠʔͷੜ੒͸ΫϥεϝιουͰ΍Δ͍ͧ sub register {

    my ($class, %args) = @_; my $row = MyApp::DB::Table::Player->insert( name => $args{name}, gender => $args{gender}, age => $args{age}, rank => 1, exp => 0, ); # ΦϒδΣΫτΛ࡞ͬͯฦ͢ return $class->new( id => $row->id, _rows => $row, ); } # usage my $new_player = MyApp::Model::Player->register( name => 'acidlemon', gender => 'male', age => 0x21 );
  27. ϩδοΫύοέʔδ # MyApp::Model::Email.pm use Moo; use Carp; use Digest::SHA qw(hmac_sha256_hex);

    has email => ( is => 'ro', required => 1, ); sub find_player { my $self = shift; my $row = MyApp::DB::Table::Player->single( email => $self->email ); croak 'cannot find user: ' . $self->email; return MyApp::Model::User->new( id => $row->id, _row => $row ); } sub calc_email_digest_hash { my $self = shift; state $salt = 'salt_from_hakata'; my $digest = hmac_sha256_hex($self->email, $salt); return $digest; }
  28. ֎෦"1*Λ༏͘͠แΈࠐΉ # MyApp::Model::VerifyPurchase.pm use Moo; use Furl; use JSON::XS; has

    furl => ( is => 'lazy', default => sub { Furl->new( timeout => 10 ); }, ); sub verify_ios { my ($self, $receipt_b64) = @_; my $payload = { 'receipt-data' => $receipt_b64 }; my $res = $self->furl->post( 'https://buy.itunes.apple.com/verifyReceipt', [], encode_json $payload, ); if ($res->status != 200) { croak 'HTTP Status Error: ' . $res->status; } my $res_json = eval { decode_json $res->body }; # ҎԼলུ… }
  29. σʔλू߹Λѻ͏ # MyApp::Model::Season::Ranking.pm use Moo; use MyApp::Model::LeaderBoard; # Redis::LeaderBoardΛwrapͨ͠΍ͭ has

    season_id => ( is => 'ro', required => 1, ); has leader_board => ( is => 'lazy', default => sub { my $leader_board = MyApp::Model::LeaderBoard->new( key => sprintf('season:ranking:%s', $self->season_id), ); } );
  30. σʔλू߹Λѻ͏ ଓ͖ # MyApp::Model::Season::Ranking.pm (ଓ͖) # Redis͔ΒRankingΛऔ͖ͬͯͯDBʹೖΕͯ֬ఆ͢Δ! sub fix_ranking {

    my $self = shift; my $rankings = $self->leader_board->rankings( offset => 0, limit => 3000, ); my $txn = MyApp::DB->txn_scope; for my $player_score (@$rankings) { MyApp::DB::Table::PlayerSeasonRanking->insert( season_id => $self->season_id, player_id => $player_score->{member}, rank => $player_score->{rank}, score => $player_score->{score}, ); } # ΄͔ʹ΋ͳΜ͔ใु͋͛ͨΓͱ͔… $txn->commit; }
  31. ͜ͷ΄͔ʹ΋ʮ͍΍ʔ͜͜೰ΜͰΔ ΜͩΑͶʔʯΈ͍ͨͳײ͡Ͱ ͩ͘Λר͍ͯ࿩ͤΔωλ͕ ͍ͬͺ͍͋Γ·͢ ࠓճ࿩͞ͳ͔ͬͨ͜ͱ7BMJEBUJPO JTBIBTBɺ4JOHMFUPOɺྫ֎ͳͲ

  32. 4FFZPV OFYUUJNF