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

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

acidlemon
December 16, 2015

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

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

acidlemon

December 16, 2015
Tweet

More Decks by acidlemon

Other Decks in Technology

Transcript

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

    (PUBOEBQNWT:PLPIBNBQN

    View full-size slide

  2. ޒ൓ాͷΈͳ͞Μ͜Μ͹Μ͸
    w ԣ඿ͷձ͔ࣾΒ΍͖ͬͯ·ͨ͠ ॅ·͍͸ח૔Ͱ͢

    w 1FSM(PMBOH$ϓϩάϥϚʔ كʹ3VCZͱ+BWBTDSJQU


    w 8FBSFIJSJOHTFFXXXLBZBDDPNSFDSVJU
    !BDJEMFNPO
    .BTBUPTIJ,BXB[PF
    4PGUXBSF&OHJOFFS
    BU,":"$*OD

    View full-size slide

  3. ۙگ
    w ࡢ೥ޙ൒͔Β೥ͪΐͬͱ͘Β͍ͣͬͱ(PΛॻ
    ͍ͯͨͷͰ͕͢ɺ࠷ۙ1FSMʹؼ͖ͬͯ·ͨ͠
    w ٱʑʹ1FSMͰ8FCαʔϏεΛॻ͍͍ͯ·͢
    w ͳΜ͔ͩΜ͍ͩͬͯखʹͳ͡ΜͰͯྑ͍

    View full-size slide

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

    View full-size slide

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

    w 03.͸%#*Y$MBTT

    Ұ෦ྫ֎༗Γʜ-PCJ͸5FOHͰ͢

    w 0014ZTUFN͸.PVTF
    w 0CKFDU$POUBJOFSʹͳΜ͔৭ʑԡ͠ࠐΜͰΔ

    ͨͱ͑͹%#3FEJT઀ଓͷӬଓԽͳͲ

    View full-size slide

  6. ৽͘͠ຊॻ͘ʹ͋ͨͬͯ
    શ෦ม͑ͯΈͨ

    View full-size slide

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

    View full-size slide

  8. "NPO5FOH΍
    .PPͷ࿩͸͠·ͤΜ

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  11. .PEFM03.
    w .PEFMͱ͸Կ͔ͱ͍͏࿩ʹͳΔͱɺ03.ͷ࿩
    ͕ग़ͯ͘Δ ྫ͑͹5FOH3PXʹͨ͘͞Μϩ
    δοΫΛੜ΍͢࿩ͱ͔͋Γ·͢ΑͶ

    w 03.͸͔֬ʹΦϒδΣΫτࢦ޲Ͱ%#Λѻ͑Δ
    ͷͰศར͚ͩͲɺϏδωεϩδοΫΛ࣮૷͢Δ
    .PEFMͦͷ΋ͷͰ͸ͳ͍ͷͰ͸ ͱࢥ͍ͬͯΔ
    w ͱ͍͏ͷ΋ετΞઌ͸%#͚ͩͰ͸ͳ͍

    View full-size slide

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

    View full-size slide

  13. શମ૾
    $POUSPMMFS
    4FSWJDF
    .PEFM
    %#3FEJTFUDʜ
    .PEFMΦϒδΣΫτΛ࡞ͬͯ
    ϩδοΫΛॱ൪ʹ࣮ߦ͢Δ
    3FRVFTUͳͲͷ7BMJEBUJPOΛͯ͠
    4FSWJDFΛݺͼग़͢
    ΦϒδΣΫτࢦ޲ʹج͍ͮͯ
    ࣗ෼ͷৼΔ·͍Λ͢Δ
    ֎෦*0Λߦͬͯ
    σʔλͷӬଓԽ΍ΩϟογϡΛߦ͏



    View full-size slide

  14. $POUSPMMFS‎4FSWJDF
    w $POUSPMMFSͷ࢓ࣄ
    w ϦΫΤετύϥϝʔλͱηογϣϯσʔλͷݕূ
    w ݕূࡁΈͷҾ਺ͷ४උ
    w ΤϯυϙΠϯτʹରԠ͢Δ4FSWJDFGVODUJPOΛݺͼ
    ग़͢
    w 4FSWJDFͷ໭Γ஋Λ࢖ͬͯϨεϙϯεͲ͏ʹ͔͢Δ
    )5.-͔+40/ΛϨϯμϦϯά͢Δ

    View full-size slide

  15. ΈΜͳ೔ຊޠΑΓ΋
    1FSMͷ΄͏͕
    ಘҙͩͱࢥ͏ͷͰɺ
    1FSMͰॻ͍͍͖ͯ·͢

    View full-size slide

  16. $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 });
    }

    View full-size slide

  17. 4FSWJDF‎.PEFM
    w 4FSWJDFͷ࢓ࣄ
    w ύϥϝʔλΛݩʹ.PEFMΦϒδΣΫτΛੜ੒
    w .PEFMΦϒδΣΫτͷϩδοΫΛݺͼग़͢
    w ϩδοΫͷ࣮ߦ݁ՌΛجʹɺ"1*࢓༷ʹଈ͠
    ͨ໭Γ஋Λ࡞Δ
    ༨ஊੲ͸ࣾ಺ͰʮҰͭͷ"1*&OEQPJOUʹରԠͯ͠Ұͭͷ.PEFMͷ&OUSZ1PJOUΛ༻ҙ͢΂͖͡ΌͶ ʯͬͯ
    ओுͯͨ͠Μ͚ͩͲɺΈΜͳʮ͑ʔʯͬͯײ͡Ͱߏͬͯ͘Εͳ͔ͬͨͷͰ4FSWJDF͍ͬͯ͏໊લۭؒʹ෼͚ͨ

    View full-size slide

  18. 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;
    }

    View full-size slide

  19. ༨ஊͷଓ͖
    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ΛݺͿ

    View full-size slide

  20. .PEFM‎%#3FEJT
    w .PEFMͷ࢓ࣄ
    w ͜͜ʹϏδωεϩδοΫΛॻ͘ʂʂʂ
    w %#ͱ͔3FEJTͱ͔Λୟ͘ʂʂ
    w ֎෦"1*΋ୟ͘ʂ
    w .PEFMͷ֎͔ΒԼ૚ ֎෦*0
    ΛӅณ͢Δ

    View full-size slide

  21. .PEFMͷྫɺ۩ମతʹ
    w 3PXͱͷύοέʔδ
    w %#ͷ3PXΛIBTͯ͠Ӆณ͢Δύλʔϯ
    w %#3FEJTΛ࢖ͬͨϩδοΫύοέʔδ
    w VTFSςʔϒϧΛFNBJMͰݕࡧɺॏෳνΣοΫΈ͍ͨͳέʔε

    %#্Ͳ͔͜ʹैଐ͍ͯ͠Δ
    FNBJMΛओମͱͯ͠ѻ͏
    w ֎෦"1*Λ༏͘͠แΈࠐΉύοέʔδ
    w ՝ۚݕূ"1*΍1VTI௨஌EBFNPOΛୟ͘ͳͲ
    w ಛఆͷ৚݅Λຬͨ͢σʔλू߹શମΛѻ͏ύοέʔδ
    w ϥϯΩϯάσʔλͳͲ

    View full-size slide

  22. .PEFMΛॻ࣌͘͜͏ͯ͠Δ
    w ΦϒδΣΫτࢦ޲Ͱॻ͘ɻ.PPͳΓ.PVTFͳΓ
    Λ࢖͏ ΋ͪΖΜ$MBTT"DDFTTPSྨͰ΋͍͍ͱ͓΋͏

    w ϑΝΠϧ͕௕͘ͳΒͳ͍Α͏ʹద౓ʹ෼ׂ͢Δ

    υϝΠϯۦಈઃܭͱ͔ࢀߟʹ͢Ε͹͍͍ͱࢥ͏

    ͱҒͦ͏ʹ͍͏ׂʹࣗ෼͸ͦͷखͷจݙͳͲΛશવ·͡ΊʹಡΜͰͳ͍ͷͰɺ
    ΦϨΦϨઃܭཧ࿦Λิڧ͢Δͱ͖͚ͩͦ͏͍͏ͷಡΜͰΔ

    View full-size slide

  23. .PEFMΛॻ࣌͘͜͏ͯ͠Δ
    w 8FC΂ͬͨΓʹͳΒͳ͍Α͏ʹॻ͘ɻ

    ͦ͏͢Ε͹$-*ͱ͔͔Β΋ݺͿͱ͖ʹࠔΒͳ͍

    .PEFMͷҾ਺ʹ͸1MBDL3FRVFTU΍8"'ͷ$POUFYUΛ௚઀౉͞ͳ͍ɺ
    தͷύϥϝʔλΛऔΓग़ͯ͠౉͢

    w ؾ࣋ͪͱͯ͠͸8"'Λ͛͢ସ͑ͯ΋.PEFM͸ͦ
    ͷ··ྲྀ༻Ͱ͖ΔΑ͏ʹ.PEFMΛ࡞Δ

    ͔͠͠ݱঢ়"NPOͳͷͰ.Z"QQDPOUFYU͔ΒECͱ͔SFEJTͷίωΫ
    γϣϯΦϒδΣΫτΛऔͬͯ͘Δ࡞Γʹͯ͠͠·͏ͱͦ͜ʹґଘ͢Δͱ͍͏ʜ

    ‎ݱ࣮తʹ8"'ม͑Δͱ͔ͦ͏ͦ͏ͳ͍Αͳ

    View full-size slide

  24. 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;
    }

    View full-size slide

  25. 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
    );

    View full-size slide

  26. ϩδοΫύοέʔδ
    # 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;
    }

    View full-size slide

  27. ֎෦"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 };
    # ҎԼলུ…
    }

    View full-size slide

  28. σʔλू߹Λѻ͏
    # 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),
    );
    }
    );

    View full-size slide

  29. σʔλू߹Λѻ͏ ଓ͖

    # 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;
    }

    View full-size slide

  30. ͜ͷ΄͔ʹ΋ʮ͍΍ʔ͜͜೰ΜͰΔ
    ΜͩΑͶʔʯΈ͍ͨͳײ͡Ͱ
    ͩ͘Λר͍ͯ࿩ͤΔωλ͕
    ͍ͬͺ͍͋Γ·͢
    ࠓճ࿩͞ͳ͔ͬͨ͜ͱ7BMJEBUJPO JTBIBTBɺ4JOHMFUPOɺྫ֎ͳͲ

    View full-size slide

  31. 4FFZPV
    OFYUUJNF

    View full-size slide