$30 off During Our Annual Pro Sale. View Details »

アクティレコードだけで戦うには、この世界は複雑過ぎる #ginzarails

アクティレコードだけで戦うには、この世界は複雑過ぎる #ginzarails

銀座Rails #2 slide

Tomohiro Hashidate

October 17, 2018
Tweet

More Decks by Tomohiro Hashidate

Other Decks in Technology

Transcript

  1. ActiveRecord
    だけで戦うには
    この世界は複雑過ぎる
    @joker1007
    銀座Rails #2

    View Slide

  2. Self­intro
    @joker1007
    Repro inc. CTO (
    要は色々やる人)
    Ruby/Rails
    fluentd/embulk
    RDB
    Docker/ECS
    Bigquery/EMR/Hive/Presto/Cassandra

    View Slide

  3. Rails
    がカバーする主戦場
    Web
    ブラウザ/
    スマホからのデータ入力
    RDB
    に永続化し、必要な時はそこから読み出す
    一人の人間が同時に必要とするデータはそこまで多くない
    出力データはHTML
    かJSON
    基本的には、これで大体のWeb
    アプリケーションの基盤は作れる

    View Slide

  4. そのRails
    の便利さを支えるのが
    ActiveRecord
    であり、
    そして、最終的に最も頼りにならない箇
    所でもある
    コントローラーがどうやってアクセスハンドリングするかとか、
    ビューがどうやってテンプレート探してるかとかは、普段のアプ
    リ開発ではそこまで意識しなくてもやっていける。

    View Slide

  5. 現実は複雑である
    弊社の様なデータ収集が基盤にあるサービスや、IoT
    のためにセン
    サーから大量のトラフィックがあるサービス、外部サービスと連
    携してデータを受け渡しするサービスでは入力ソースは多岐に渡
    る。
    S3/SQS
    Kinesis/Kafka
    MQTT
    弊社では顧客アプリケーションからのセッションデータ受け取り
    にS3
    とSQS
    の連携を利用している。

    View Slide

  6. データソースがRDB
    じゃない世界
    受け取って、そのままRDB
    に流して済むならAR
    に渡せばいい
    しかし、そう簡単に行かないこともある
    レガシーデータとの整合性を保つためのデータ変換
    データ取得の堅牢性
    エラー処理とリトライハンドリング
    ロギング
    パフォーマンス要求
    将来的な言語移植の可能性
    そもそも出力先がRDB
    でないケース
    それなりのロジックが必要になることも多い。

    View Slide

  7. 最終的にRDB
    に入るのであれば、
    ActiveRecord
    モデルのクラスに書いてし
    まってもやれないことはない……

    が、そうすると責務が肥大化して凝集度
    が下がるし、色々な所と結合してめっち
    ゃテストがしづらくなる。

    View Slide

  8. AR
    ではないモデルを用意する
    弊社ではSQS
    をデータソースとしたモデルがある
    フラットに置くかネームスペースを切るかはコーディングスタイ
    ルに依る

    View Slide

  9. Example
    c
    l
    a
    s
    s S
    e
    s
    s
    i
    o
    n
    M
    e
    s
    s
    a
    g
    e
    c
    l
    a
    s
    s <
    < s
    e
    l
    f
    d
    e
    f d
    e
    q
    u
    e
    (
    &
    b
    l
    o
    c
    k
    )
    p
    o
    l
    l
    e
    r
    .
    p
    o
    l
    l
    (
    s
    k
    i
    p
    _
    d
    e
    l
    e
    t
    e
    : t
    r
    u
    e
    ) d
    o |
    m
    s
    g
    |
    d
    a
    t
    a = O
    j
    .
    l
    o
    a
    d
    (
    m
    s
    g
    .
    b
    o
    d
    y
    )
    b
    e
    g
    i
    n
    y
    i
    e
    l
    d n
    e
    w
    (
    d
    a
    t
    a
    )
    p
    o
    l
    l
    e
    r
    .
    d
    e
    l
    e
    t
    e
    _
    m
    e
    s
    s
    a
    g
    e
    (
    m
    s
    g
    )
    r
    e
    s
    c
    u
    e =
    > e
    R
    a
    i
    l
    s
    .
    l
    o
    g
    g
    e
    r
    .
    w
    a
    r
    n
    (
    e
    )
    e
    n
    d
    e
    n
    d
    e
    n
    d
    p
    r
    i
    v
    a
    t
    e d
    e
    f p
    o
    l
    l
    e
    r
    # o
    m
    i
    t
    e
    n
    d
    e
    n
    d
    e
    n
    d

    View Slide

  10. こういったモデルを作る時に便利
    Struct + ActiveModel::Validations
    ActiveModel/Model + ActiveModel::Attributes

    View Slide

  11. Struct
    JSON ­> Hash
    の一歩先へ
    k
    e
    y
    w
    o
    r
    d
    _
    i
    n
    i
    t
    : t
    r
    u
    e
    でめっちゃ使い易くなった
    Rails
    に依存しない
    処理が軽い
    C
    レベルで定義が完了するため、普通にクラス定義するよ
    り1.2
    から1.5
    倍ぐらい早い
    バリデーションやデフォルト値の定義先を明確にできる
    単体テストが簡単

    View Slide

  12. ActiveModel::Attributes
    ついにRails
    公式で、ActiveModel
    にリッチアトリビュートが
    型変換
    デフォルト値 (proc
    対応)
    カスタムタイプキャスター対応
    値オブジェクトとの親和性
    同一性の検証
    透過的なシリアライズ/
    デシリアライズ
    雑にJSON
    からマッピングしても、型やデフォルト値を上手くマッ
    ピングしてくれる
    ちなみにRails5.2
    より前に、自分でタイプキャストしてStruct
    にマ
    ッピングしているコードが結構ある

    View Slide

  13. 中間まとめ
    モデルといってもデータソースは様々
    SQS
    とかKinesis
    とかKafka
    とかだったり
    シリアライズされたものからオブジェクトにマッピングする
    複雑な構造のデータは単純にHash
    に割り当てずに、ちゃんと
    クラスを定義する
    タイプキャストをネストすれば、入れ子構造にも対応できる
    複雑なモデルはツリー構造になる
    オブジェクトマッピングの処理とドメインロジックをごっち
    ゃにしない
    データソースとのやり取り、値変換、バリデーション、メイ
    ンロジックが独立してテストできる様に、クラスやメソッド
    を分割する

    View Slide

  14. 汎用のマッパーまでやるか?
    例えばRedis
    とかは汎用のオブジェクトマッパーのgem
    がある。
    gem
    化するとか、3
    つ以上そういうモデルがあるならやってもいい
    けど、その必要が無いことも多いと思う。
    重要なのは抽象化ではなく、責務が独立していてテスト境界が明
    確であること。

    View Slide

  15. ここまでは主に入力について
    続いて出力についての話

    View Slide

  16. ActiveRecord
    でやってはいけないこと
    代表格が集計
    何故かというと、単純に効率がめちゃくちゃ悪いから。
    ActiveRecord
    の様なオブジェクト作るだけで重いもので大量
    にデータを読み込むものではない
    基本的にシングルスレッドでしか動かないのでリソースを効
    率良く使うのが難しい
    出力も正規化とは程遠かったりする
    つまり、SQL
    を書くのをサボってはいけないということ

    View Slide

  17. だからって集計用のSQL
    をArel
    でゴリゴ
    リ組み立てるとか絶対地獄なので止めま
    しょう
    やっていいのは簡単なクエリだけ
    SQL
    を部品ごとに再利用しようなんてのは幻想
    また、完結したクエリ単位ならある程度再利用できる

    View Slide

  18. SQL
    を書く時に良く使うもの
    #select
    #find_by_sql
    ConnectionAdapter
    の#execute

    ERB

    View Slide

  19. select
    をちゃんと使う
    AR
    のメソッドだけでもある程度の集計をSQL
    にやらせて結果を受
    け取ることができる。
    r
    e
    s
    u
    l
    t = U
    s
    e
    r
    .
    l
    e
    f
    t
    _
    o
    u
    t
    e
    r
    _
    j
    o
    i
    n
    s
    (
    p
    o
    s
    t
    s
    : :
    r
    a
    t
    e
    s
    )
    .
    s
    e
    l
    e
    c
    t
    (
    "
    C
    O
    U
    N
    T
    (
    D
    I
    S
    T
    I
    N
    C
    T r
    a
    t
    e
    s
    .
    u
    s
    e
    r
    _
    i
    d
    ) A
    S r
    a
    t
    e
    d
    _
    u
    s
    e
    r
    s
    "
    ,
    "
    I
    F
    (
    r
    a
    t
    e
    s
    .
    u
    s
    e
    r
    _
    i
    d I
    S N
    U
    L
    L
    , 0
    ,
    M
    I
    N
    (
    r
    a
    t
    e
    s
    .
    r
    e
    v
    i
    e
    w
    )
    ) A
    S m
    i
    n
    _
    r
    e
    v
    i
    e
    w
    "
    ,
    "
    M
    A
    X
    (
    r
    a
    t
    e
    s
    .
    r
    e
    v
    i
    e
    w
    ) A
    S m
    a
    x
    _
    r
    e
    v
    i
    e
    w
    "
    ,
    "
    A
    V
    G
    (
    r
    a
    t
    e
    s
    .
    r
    e
    v
    i
    e
    w
    ) A
    S a
    v
    g
    _
    r
    e
    v
    i
    e
    w
    "
    ,
    )
    .
    g
    r
    o
    u
    p
    (
    :
    i
    d
    )
    .
    t
    a
    k
    e
    !
    p r
    e
    s
    u
    l
    t
    .
    c
    l
    a
    s
    s #
    =
    > U
    s
    e
    r
    p r
    e
    s
    u
    l
    t
    .
    r
    a
    t
    e
    d
    _
    u
    s
    e
    r
    s # =
    >
    レーティングした人の数
    ただ、簡易なものに留めておくのが無難。
    そして、出来るだけSQL
    そのものの構造に似せて書いた方が後々
    剥がす時に楽。

    View Slide

  20. find_by_sql
    でのINLINE
    埋め込み
    r
    e
    s
    u
    l
    t = U
    s
    e
    r
    .
    f
    i
    n
    d
    _
    b
    y
    _
    s
    q
    l
    (
    [
    <
    <
    ~
    S
    Q
    L
    , a
    u
    t
    h
    o
    r
    i
    z
    e
    d
    : 1
    ]
    )
    S
    E
    L
    E
    C
    T
    C
    O
    U
    N
    T
    (
    D
    I
    S
    T
    I
    N
    C
    T
    C
    A
    S
    E W
    H
    E
    N r
    a
    t
    e
    s
    .
    a
    u
    t
    h
    o
    r
    i
    z
    e
    d = :
    a
    u
    t
    h
    o
    r
    i
    z
    e
    d T
    H
    E
    N
    r
    a
    t
    e
    s
    .
    u
    s
    e
    r
    _
    i
    d
    E
    L
    S
    E
    N
    U
    L
    L
    E
    N
    D
    ) A
    S r
    a
    t
    e
    d
    _
    u
    s
    e
    r
    s
    ,
    M
    I
    N
    (
    r
    a
    t
    e
    s
    .
    r
    e
    v
    i
    e
    w
    ) A
    S m
    i
    n
    _
    r
    e
    v
    i
    e
    w
    ,
    M
    A
    X
    (
    r
    a
    t
    e
    s
    .
    r
    e
    v
    i
    e
    w
    ) A
    S m
    a
    x
    _
    r
    e
    v
    i
    e
    w
    ,
    A
    V
    G
    (
    r
    a
    t
    e
    s
    .
    r
    e
    v
    i
    e
    w
    ) A
    S a
    v
    g
    _
    r
    e
    v
    i
    e
    w
    F
    R
    O
    M u
    s
    e
    r
    s
    L
    E
    F
    T O
    U
    T
    E
    R J
    O
    I
    N p
    o
    s
    t
    s O
    N
    u
    s
    e
    r
    s
    .
    i
    d = p
    o
    s
    t
    s
    .
    u
    s
    e
    r
    _
    i
    d
    L
    E
    F
    T O
    U
    T
    E
    R J
    O
    I
    N r
    a
    t
    e
    s O
    N
    p
    o
    s
    t
    s
    .
    i
    d = r
    a
    t
    e
    s
    .
    p
    o
    s
    t
    _
    i
    d
    S
    Q
    L

    View Slide

  21. execute with ERB
    I
    N
    S
    E
    R
    T I
    N
    T
    O a
    g
    g
    r
    e
    g
    a
    t
    e
    d
    _
    s
    e
    s
    s
    i
    o
    n
    s
    S
    E
    L
    E
    C
    T D
    I
    S
    T
    I
    N
    C
    T
    i
    n
    s
    i
    g
    h
    t
    _
    i
    d
    , u
    n
    i
    t
    , s
    t
    a
    r
    t
    e
    d
    _
    a
    t
    , c
    u
    s
    t
    o
    m
    _
    e
    v
    e
    n
    t
    _
    i
    d
    , S
    U
    M
    (
    C
    A
    S
    E W
    H
    E
    N p
    l
    a
    t
    f
    o
    r
    m I
    N (
    '
    i
    o
    s
    '
    , '
    a
    n
    d
    r
    o
    i
    d
    '
    , '
    w
    e
    b
    '
    ) T
    H
    E
    N
    , S
    U
    M
    (
    C
    A
    S
    E W
    H
    E
    N p
    l
    a
    t
    f
    o
    r
    m = '
    i
    o
    s
    ' T
    H
    E
    N f
    r
    e
    q
    u
    e
    n
    c
    y E
    L
    S
    E 0 E
    N
    D
    , S
    U
    M
    (
    C
    A
    S
    E W
    H
    E
    N p
    l
    a
    t
    f
    o
    r
    m = '
    a
    n
    d
    r
    o
    i
    d
    ' T
    H
    E
    N f
    r
    e
    q
    u
    e
    n
    c
    y E
    L
    S
    E
    , S
    U
    M
    (
    C
    A
    S
    E W
    H
    E
    N p
    l
    a
    t
    f
    o
    r
    m = '
    w
    e
    b
    ' T
    H
    E
    N f
    r
    e
    q
    u
    e
    n
    c
    y E
    L
    S
    E 0 E
    N
    D
    F
    R
    O
    M `
    a
    g
    g
    r
    e
    g
    a
    t
    e
    d
    _
    s
    e
    s
    s
    i
    o
    n
    s
    _
    <
    %
    = u
    n
    i
    t %
    >
    `
    W
    H
    E
    R
    E
    d
    t >
    = '
    <
    %
    = f
    r
    o
    m
    .
    s
    t
    r
    f
    t
    i
    m
    e
    (
    "
    %
    Y
    %
    m
    %
    d
    "
    ) %
    >
    '
    A
    N
    D d
    t < '
    <
    %
    = t
    o
    .
    s
    t
    r
    f
    t
    i
    m
    e
    (
    "
    %
    Y
    %
    m
    %
    d
    "
    ) %
    >
    '
    A
    N
    D f
    i
    r
    s
    t
    _
    a
    c
    c
    e
    s
    s = f
    a
    l
    s
    e
    G
    R
    O
    U
    P B
    Y
    i
    n
    s
    i
    g
    h
    t
    _
    i
    d
    , u
    n
    i
    t
    , c
    o
    n
    v
    e
    r
    s
    i
    o
    n
    _
    s
    t
    a
    r
    t
    e
    d
    _
    a
    t
    , c
    u
    s
    t
    o
    m
    _
    e
    v
    e
    n
    t
    _
    i
    d

    View Slide

  22. c
    l
    a
    s
    s Q
    u
    e
    r
    y
    R
    e
    n
    d
    e
    r
    e
    r < O
    p
    e
    n
    S
    t
    r
    u
    c
    t
    d
    e
    f s
    e
    l
    f
    .
    r
    e
    n
    d
    e
    r
    (
    t
    e
    m
    p
    l
    a
    t
    e
    _
    f
    i
    l
    e
    , *
    *
    v
    a
    r
    i
    a
    b
    l
    e
    s
    )
    n
    e
    w
    (
    t
    e
    m
    p
    l
    a
    t
    e
    _
    f
    i
    l
    e
    : t
    e
    m
    p
    l
    a
    t
    e
    _
    f
    i
    l
    e
    , *
    *
    v
    a
    r
    i
    a
    b
    l
    e
    s
    )
    .
    r
    e
    n
    d
    e
    r
    e
    n
    d
    d
    e
    f r
    e
    n
    d
    e
    r
    E
    R
    B
    .
    n
    e
    w
    (
    F
    i
    l
    e
    .
    r
    e
    a
    d
    (
    t
    e
    m
    p
    l
    a
    t
    e
    _
    f
    i
    l
    e
    )
    , n
    i
    l
    , "
    ­
    "
    )
    .
    r
    e
    s
    u
    l
    t
    (
    b
    i
    n
    d
    i
    n
    g
    )
    e
    n
    d
    e
    n
    d
    r
    e
    s
    u
    l
    t = A
    c
    t
    i
    v
    e
    R
    e
    c
    o
    r
    d
    :
    :
    B
    a
    s
    e
    .
    c
    o
    n
    n
    e
    c
    t
    i
    o
    n
    .
    e
    x
    e
    c
    u
    t
    e
    (
    Q
    u
    e
    r
    y
    R
    e
    n
    d
    e
    r
    e
    r
    .
    r
    e
    n
    d
    e
    r
    (
    t
    e
    m
    p
    l
    a
    t
    e
    _
    f
    i
    l
    e
    , {
    u
    n
    i
    t
    : "
    d
    a
    y
    "
    , f
    r
    o
    m
    : 1
    .
    d
    a
    y
    .
    a
    g
    o
    , t
    o
    : T
    i
    m
    e
    .
    c
    u
    r
    r
    e
    n
    t
    }
    )
    ,
    )

    View Slide

  23. 後者になる程、生SQL
    として個別に管理
    しやすくなる
    この手の集計・分析処理は高負荷であ
    り、データ量が増えるとすぐに破綻する
    特化したDWH
    か分散処理基盤が必要にな
    った時にSQL
    をベースにしておけば、移
    行がしやすい

    View Slide

  24. バッチの依存関係
    集計に限らず、時間のかかるバッチ処理は複数のジョブによって
    構成され、処理同士に依存関係がある場合が多い。

    View Slide

  25. ワークフローツールの導入
    ツールに必要な要素
    処理の依存関係と処理自体の定義を分離する
    依存関係が一見して確認できることが望ましい
    依存関係の無い処理は並列に実行できる
    複数の処理の終了を待ち受けてから処理を開始できる
    任意の処理から実行を再開できる
    オプショナルな要素
    実行スケジュールの管理ができる
    ワーカーとフローのコントローラを分離できる
    Web UI
    コンテナ対応

    View Slide

  26. 最近選択肢が増えてきている
    ツール/
    ソフトウェア
    rukawa (
    拙作)
    digdag
    airflow
    luiji
    Jenkins
    サービス
    AWS Batch
    CircleCI
    Github Actions (NEW)

    View Slide

  27. バッチ処理の基本
    一つ一つの処理は独立して実行可能な1
    目的の処理にする
    処理結果が羃等(
    繰り返し実行しても同じ結果)
    になる様にする
    複数の処理を組み合わせて結果的に羃等でも良い
    途中からリトライできる様にする
    どこまで実行できたか、簡単に把握できる様にする
    エラー通知ができる様にする
    過去のものを再実行できる様にする

    View Slide

  28. 弊社の例
    rukawa + Amazon Fargate

    View Slide

  29. rukawa
    の活用
    https://github.com/joker1007/rukawa
    Rails
    アプリから成長してきたので、いくつかのデータやロジ
    ックをRails
    のapp/models
    から参照している
    Ruby
    で直接コントロールできて、Rails
    をそのまま読み込める
    rukawa
    を作ったのはそういう理由に依る
    ヘルパーメソッドを定義して、集計時刻等のパラメーターを
    渡し易くしている
    大体の処理はテンプレートを元にクエリをレンダリングして
    Bigquery
    やHive/Presto
    等にリクエストをする

    View Slide

  30. c
    l
    a
    s
    s W
    o
    r
    k
    f
    l
    o
    w
    S
    a
    m
    p
    l
    e < R
    u
    k
    a
    w
    a
    :
    :
    J
    o
    b
    N
    e
    t
    d
    e
    f s
    e
    l
    f
    .
    d
    e
    p
    e
    n
    d
    e
    n
    c
    i
    e
    s
    {
    A
    g
    g
    r
    e
    g
    a
    t
    e
    d
    C
    l
    i
    p
    s
    D
    a
    y =
    > [
    ]
    ,
    C
    o
    n
    v
    e
    r
    s
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y =
    > [
    A
    g
    g
    r
    e
    g
    a
    t
    e
    d
    C
    l
    i
    p
    s
    D
    a
    y
    ]
    ,
    E
    x
    p
    o
    r
    t
    C
    o
    n
    v
    e
    r
    s
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y =
    > [
    C
    o
    n
    v
    e
    r
    s
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y
    ]
    ,
    C
    o
    p
    y
    C
    o
    n
    v
    e
    r
    s
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y =
    > [
    E
    x
    p
    o
    r
    t
    C
    o
    n
    v
    e
    r
    s
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y
    ]
    ,
    I
    m
    p
    o
    r
    t
    C
    o
    n
    v
    e
    r
    s
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y =
    > [
    C
    o
    p
    y
    C
    o
    n
    v
    e
    r
    s
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y
    ]
    ,
    R
    e
    t
    e
    n
    t
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y =
    >
    [
    A
    g
    g
    r
    e
    g
    a
    t
    e
    d
    C
    l
    i
    p
    s
    D
    a
    y
    , C
    o
    n
    v
    e
    r
    s
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y
    ]
    ,
    E
    x
    p
    o
    r
    t
    R
    e
    t
    e
    n
    t
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y =
    > [
    R
    e
    t
    e
    n
    t
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y
    ]
    ,
    C
    o
    p
    y
    R
    e
    t
    e
    n
    t
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y =
    > [
    E
    x
    p
    o
    r
    t
    R
    e
    t
    e
    n
    t
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y
    ]
    ,
    I
    m
    p
    o
    r
    t
    R
    e
    t
    e
    n
    t
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y =
    > [
    C
    o
    p
    y
    R
    e
    t
    e
    n
    t
    i
    o
    n
    F
    a
    c
    t
    s
    D
    a
    y
    ]
    ,
    }
    e
    n
    d
    e
    n
    d

    View Slide

  31. Amazon Fargate
    EC2
    インスタンス無しで、コンテナ上で処理を実行できる
    必要なタイミングでECS
    のAPI
    を叩いて、特定のイメージでコ
    マンドを実行するジョブをrukawa
    で定義する
    コマンド引数や環境変数、S3
    等を利用してデータを引き
    渡す
    セキュリティはTask Role
    でコントロールする
    クリーンで運用負荷の無いジョブ実行環境が、Fargate
    の制限
    に到達するまではいくつでも並行に用意できる
    アプリイメージの外にembulk
    やrclone
    やhive
    クライアント等の
    コマンドラインツールを用意して独立して管理できる

    View Slide

  32. wrapbox
    https://github.com/reproio/wrapbox
    Ruby
    からECS
    のAPI
    を叩いてコンテナ上でコマンドを実行して、
    終了を待ち受けるgem
    hako oneshot
    みたいなもの
    元々はECS
    上でRSpec
    を実行するために作ったが、Fargate
    が日本
    に来たタイミングでバッチ処理と相性が良さそうだったため、改
    修してFargate
    対応した
    rukawa
    と組み合わせることで、並列度の高いバッチワークフロー
    実行環境が低価格で手に入った

    View Slide

  33. 余談 (
    時間があれば口頭で)
    fluentd
    への転送処理の実装上の工夫
    cassandra
    の使い方
    presto
    とRDB
    のデータの組み合わせ

    View Slide

  34. まとめ
    昨今のWeb
    アプリケーションは、RDB
    に入れて出して済む程に簡
    単ではない
    多くの複雑な問題に対処するためには、適切な解決方法を常に選
    択し続けていく必要がある
    実装上の工夫をしたり、採用ツールの枠を広げたり、新しいサー
    ビスを活用したりして、新しい問題に立ち向かっていく態勢を準
    備する必要がある
    今日話しをしたのはその一例に過ぎない
    大きく成長するアプリケーションを支える開発者は色々と考える
    ことが多いが、そこが面白いところでもある

    View Slide