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

Upgrow: Railsアプリの保守性を高めるためのShopifyのアプローチ / Upgrow

Upgrow: Railsアプリの保守性を高めるためのShopifyのアプローチ / Upgrow

Daiki Miura

April 23, 2021
Tweet

More Decks by Daiki Miura

Other Decks in Technology

Transcript

  1. Upgrow:
    Railsアプリの保守性を高めるための
    Shopifyのアプローチ
    2021-04-23
    銀座Rails #32
    Daiki Miura

    View full-size slide

  2. 自己紹介
    ▶ 名前: 三浦 大輝 (Miura Daiki)
    ▶ GitHub: @daikimiura
    ▶ Twitter: @daikiii5555
    ▶ 最近の趣味: 車輪の再発明
            (自作Docker、自作OS、自作Cコンパイラ etc)
    ▶ PINTORという美術鑑賞アプリの会社で働いています
    ▶ この前のISUCONでRubyで決勝進出しました🎉

    View full-size slide

  3. 目的
    1. Upgrowとはどんなアーキテクチャで、どんな課題をどう解決しようとしているのかを
    説明する
    2. Upgrowと既存のアプローチの共通点・相違点を明らかにする
    3. 1., 2. を踏まえた上でどのようにアーキテクチャ選定を行えば良いかの方針をまと
    める

    View full-size slide

  4. 目次
    1. Upgrowとは何か?
    a. 解決しようとした課題か?
    b. その課題をどう解決しようとしたのか?
    2. 同じ課題に対する既存のアプローチとの共通点・相違点は何か?
    a. Railsでよく使われるアプローチとの違い
    b. Rails以外でよく使われるアーキテクチャとの違い
    3. まとめ

    View full-size slide

  5. 注意
    現在Upgrowの公式GitHubレポジトリ及び公式ドキュメントは非公開になっており見るこ
    とができません。
    この発表は2021年2月25日時点での情報を元に作成しています。

    View full-size slide

  6. 1. Upgrowとは何か?
       解決しようとした課題か?
       その課題をどう解決しようとしたのか?

    View full-size slide

  7. ソフトウェアアーキテクチャのゴール
    「そもそもなんのためにソフトウェアアーキテクチャを適用するのか?」
    ● 保守性の高いソフトウェアを構築するため
    ○ ⇔ コードの変更・追加を素早く簡単に行える
    ソフトウェアを構築するため
    ■ どこを変更 / どこに追加 すればいいのかが明確
    ■ 変更・追加したことによる副作用が小さい
    ■ 変更・追加する行数が少ない

    View full-size slide

  8. Upgrowが解決しようとした課題
    - “Rails Way” で開発してるといくつかの課題が生じ、保守性が下がってしまった
    - “Rails Way” = Railsが提供しているコンポーネントのみで開発する手法
    => Upgrowという独自のアーキテクチャを導入して解決した
    shopifyでは

    View full-size slide

  9. Upgrowが解決しようとした課題 (Controller編)
    - 複雑化したController Filter (before_action)
    - 条件付きのbefore_action (:if, :unless)
    - Controllerのサブセットにのみ適用される before_action (:only, :except)
    - Fat Controller
    - どこに書いたらいいか明確ではないロジックが全て Controllerに書かれる
    - 複数のModelを触る場合の一連の手続き
    - メール送信
    - 外部サービス呼び出し
    - ジョブをエンキュー
    - 上のようなロジックを ControllerではなくModelに書いてFat Modelになる場合も多い

    View full-size slide

  10. Upgrowが解決しようとした課題 (Model編)
    - Active RecordのCallback地獄
    - 大量のcallback
    - 条件付きのcallback
    - 親クラスから継承されたcallback
    - Fat Model
    - Modelに大量のメソッドが生える
    - どこに書いたらいいか分からないロジック
    - Validation
    - Callback
    - “神Model”、”神Class”
    - Active RecordのAssociation
    - 不必要にModel同士を密結合にしている
    - あるModelのメソッドが別のModelのインスタンスを触る余地を常に与えている
    - 原則、ModelのメソッドはそのModelに関する処理のみであるべき
    - associationは遅延ロードされる => N+1 問題を引き起こす

    View full-size slide

  11. そもそもRails Wayの何が問題なのか?
    - ControllerにもModelにも書けない (書きたくない) ロジックがある
    - Modelが責務を負いすぎている

    View full-size slide

  12. そもそもRails Wayの何が問題なのか?
    - ControllerにもModelにも書けない (書きたくない) ロジックがある
    - 複数のModelを触る場合の一連の手続き
    - ある特定のケースでのみ行われる Modelの処理 (特定の場合のみ行われるバリデーションなど )
    - メール送信
    - 外部サービス呼び出し
    - ジョブをエンキュー
    - => Clean Architectureでいうところのアプリケーションビジネスルール
    - ビジネスロジック (ルール) には2種類ある
    - エンタープライズビジネスルール
    - アプリケーションの都合ではないコアなルール
    - 特定のアプリケーション操作が変更しても影響を受けないロジック
    - アプリケーションビジネスルール
    - アプリケーション固有のルール
    - システムのユースケース

    View full-size slide

  13. そもそもRails Wayの何が問題なのか?
    - Modelが責務を負いすぎている
    - Controller
    - アプリ外部とのインターフェース
    - HTTPの仕様を扱う
    - View
    - HTTPレスポンスボディを組み立てる
    - Controllerの責務の一部を切り出したもの
    - Model
    - ControllerとViewが負っている責務以外の全ての責務
    - ビジネスロジックの記述
    - DBへの永続化
    - ユーザーからの入力値のバリデーション
    - コールバック
    - etc

    View full-size slide

  14. 課題をどのように解決しようとしたか?
    - 「単一の責任を持つ小さなオブジェクト同士がメッセージを交換して処理を行う」よう
    にする
    - 単一責任原則 (SRP) に沿って 既存のコンポーネントを分解・新たなコンポーネントを追
    加 していく
    - ⇔ コンポーネントに変更を加える理由がただ
    1つになるように 既存のコンポーネント
    を分解・新たなコンポーネントを追加 していく

    View full-size slide

  15. 課題をどのように解決しようとしたか?
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB
    View
    Controller
    DB
    Model
    Rails Way Upgrow

    View full-size slide

  16. Upgrowのアーキテクチャ (Model)
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB
    責務: アプリのコアなビジネスロジックの記述
    - エンタープライズビジネスルールの記述
    - インスタンスはImmutable
    - Clean Architectureで言うところのEntity
    - DBとのやり取りやValidationなどの責務は負わない
    - ActiveRecord::Baseを継承しない

    View full-size slide

  17. Upgrowのアーキテクチャ (Model)
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB
    責務: アプリのコアなビジネスロジックの記述

    View full-size slide

  18. Upgrowのアーキテクチャ (Record)
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB
    責務: DBとのやりとり
    - データベースの値を Rubyでそのまま表現したもの
    - DBとのやりとり以外のロジックは一切持たない
    - Active Recordパターンから外れる
    - Row Data Gateway?
    - ActiveRecord::Baseを継承する

    View full-size slide

  19. Upgrowのアーキテクチャ (Record)
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB
    責務: DBとのやりとり

    View full-size slide

  20. Upgrowのアーキテクチャ (Repository)
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB
    責務: アプリ側から見た永続化レイヤ
    - 永続化技術 (DBとのやりとり) を隠蔽する
    - 複数の永続化技術へのやりとりを共通化する
    - immutableなオブジェクトを返す (Model)
    - Recordからの返り値 (ARインスタンス) を
    immutableな別のオブジェクトにマッピングする

    View full-size slide

  21. Upgrowのアーキテクチャ (Repository)
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB
    責務: アプリ側から見た永続化レイヤ

    View full-size slide

  22. Upgrowのアーキテクチャ (Input)
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB
    責務: ユーザーの入力を検証しオブジェクト化する
    - 入力のバリデーションを行う
    - ActiveModel::Modelをincludeすることで
    validationのための便利メソッドやviewとのシーム
    レスな統合が行える

    View full-size slide

  23. Upgrowのアーキテクチャ (Input)
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB
    責務: ユーザーの入力を検証しオブジェクト化する
    - 入力のバリデーションを行う
    - ActiveModel::Modelをincludeすることで
    validationのための便利メソッドやviewとのシーム
    レスな統合が行える

    View full-size slide

  24. Upgrowのアーキテクチャ (Action)
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB
    責務: アプリケーション固有のロジックを記述する
    - ユースケースを記述
    - リクエストと1対1対応する
    - パブリックなメソッドはただ1つのみ
    (Action#perform)
    - Result (後述) を返す

    View full-size slide

  25. Upgrowのアーキテクチャ (Result)
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB
    責務: Actionの結果を表すオブジェクト
    - いくつかの決まったフィールドを持つ
    Struct
    - successかfailureのいずれか状態を表す
    - failureのときはerrorsフィールドに値が入る

    View full-size slide

  26. Upgrowのアーキテクチャ (Result)
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB

    View full-size slide

  27. Upgrowのアーキテクチャ (Action)

    View full-size slide

  28. Upgrowのアーキテクチャ (Controller)

    View full-size slide

  29. Upgrowのアーキテクチャ
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB

    View full-size slide

  30. Upgrowのアーキテクチャ (Association)

    View full-size slide

  31. Upgrowのアーキテクチャ (Transaction)

    View full-size slide

  32. 2. 同じ課題に対する既存のアプローチ
    との共通点・相違点何か?
       

    View full-size slide

  33. “同じ課題”
    - Controller
    - 複雑化したController Filter (before_action)
    - Fat Controller
    - Model
    - Active Recordのcallback地獄
    - Fat Model

    View full-size slide

  34. 同じ課題に対する既存のアプローチ
    Rails Developers Meetup 2019 「Ruby on Railsの正体と向き合い方」 by @yasaichi さん

    View full-size slide

  35. 同じ課題に対する既存のアプローチ
    Rails Developers Meetup 2019 「Ruby on Railsの正体と向き合い方」 by @yasaichi さん

    View full-size slide

  36. Upgrowと既存のアプローチとの比較
    - 課題の分析 (なぜ課題が生じるか?)
    - Upgrow
    - ControllerにもModelにも書けない (書きたくない) ロジックがあるから
    - Modelが責務を負いすぎているから
    - 既存のアプローチ
    - 特定のユースケースと密結合した処理を書く場所がないから

    View full-size slide

  37. 同じ課題に対する既存のアプローチとの比較
    Record
    Repository
    Action
    Input
    Model
    View
    Result
    Controller
    DB
    Model
    View
    Controller
    DB
    アプリケーション
    ビジネスルールを記述するレイヤ
    (Service, Form, Interactor)
    共通点: アプリケーションビジネスルールを記述するコンポーネントを追加したこと
    相違点: Upgrowの方が既存のアプローチよりもコンポーネントの粒度が細かい
    既存のアプローチ Upgrow

    View full-size slide

  38. UpgrowとClean Architecture (同心円状アーキテクチャ) との比較
    - UpgrowのアーキテクチャはClean Architecture (の一例である同心円状アーキテクチャ
    ) と似
    ている
    Clean Architecture Upgrow
    Entities Model
    Use Cases Action
    Controllers Controller
    Gateways Repository
    DB Record

    View full-size slide

  39. UpgrowとClean Architecture (同心円状アーキテクチャ) との比較
    - UpgrowのアーキテクチャはClean Architecture (の一例である同心円状アーキテクチャ
    ) と似
    ている
    - => Clean Architectureに従うメリットを享受できる
    - フレームワーク非依存
    - システムをフレームワークの制約で縛るのではなく、フレームワークをツールとして利
    用できる
    - データベース非依存
    - DBを簡単に差し替え可能
    - ビジネスルールはデータベースに束縛されない
    - 外部エージェント非依存
    - ビジネスルールは外界のインターフェイスに束縛されない
    - REST API, GraphQL, gRPC
    - テスト可能
    - UI, DB, Webサーバーなどの外部要素がなくてもビジネスロジックのテストを行える

    View full-size slide

  40. Railsの思想とClean Architectureの思想 (Upgrow) との比較
    - Clean Architectureに従うメリットはRailsの思想と反している部分が多い
    - フレームワーク非依存
    - システムをフレームワークの制約で縛るのではなく、フレームワークをツールとして利用でき

    => Railsは「設定より規約」
      システム全体として規約にのっとってプログラミングすることで、余計なプログラ
      ミングや設定を省く

    - データベース非依存
    - ビジネスルールはデータベースに束縛されない
    => RailsはURLからDBまでを密結合にする
    - 外部エージェント非依存
    - ビジネスルールは外界のインターフェイスに束縛されない
    => RailsはRESTなwebアプリケーションを作るためのフレームワーク

    View full-size slide

  41. Railsの思想とClean Architectureの思想 (Upgrow) との比較
    Rails
    Clean Architecture
    (Upgrow)
    フレームワークに対する考え

    フルスタックフレームワーク
    基本的にはRails Wayに
    沿って作ればいい (Rails is
    omakase)
    フレームワークは詳細 (詳細
    には依存しない)
    システム全体の設計思想
    全体として密結合な設計に
    し、規約で縛ることでコード
    量を減らす
    全体として疎結合な設計に
    するが、コード量は増える

    View full-size slide

  42. Railsの思想とClean Architectureの思想 (Upgrow) との比較
    このドキュメント(Upgrow) は私を当惑させる。私は Upgrowが
    今の形のまま長く使われるようになるとは思わない。

    View full-size slide

  43. 3. まとめ

    View full-size slide

  44. Railsの課題にどう立ち向かうのか?
    - 「これを使えば未来永劫問題なし」のような万能アーキテクチャはない
    - Rails Wayが価値を発揮する状況と他のアプローチが価値を発揮する状況は異なる
    - プロダクト (ビジネス) の複雑さの増大に従ってリアーキテクチャが必要
    - 課題を感じた時に初めてRails Wayから外れる
    - Rails Wayで課題を感じていない場合はRails Wayに乗っかるのが最適
    - まだビジネスがそこまで複雑化しておらずビジネス的な不確実性が大きいので、長期的なアプリの保守性の高さよりも
    短期的なスピードが求められる
    - 課題を感じたらまずは課題を明確にする
    - なぜFat Modelなのか?なぜFat Controllerなのか?
    - プログラミング (設計) の原則に従って、その課題を解決するような新たな必要最小限のコンポーネントを追加する
    - SOLID原則
    - オブジェクト指向設計の原則
    - 「課題を感じる => コンポーネント追加 => 課題を感じる => コンポーネントを追加 => ....」を繰り返す
    - 新たに追加したコンポーネントをチーム全員が正しく使えるようにする
    - 設計を明文化して (ドキュメントに落として) 周知する
    - gemでインターフェースを縛る

    View full-size slide

  45. Thank you for listening!

    View full-size slide