Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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 問題を引き起こす

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Railsの思想とClean Architectureの思想 (Upgrow) との比較 - Clean Architectureに従うメリットはRailsの思想と反している部分が多い - フレームワーク非依存 - システムをフレームワークの制約で縛るのではなく、フレームワークをツールとして利用でき る => Railsは「設定より規約」   システム全体として規約にのっとってプログラミングすることで、余計なプログラ   ミングや設定を省く 
 - データベース非依存 - ビジネスルールはデータベースに束縛されない => RailsはURLからDBまでを密結合にする - 外部エージェント非依存 - ビジネスルールは外界のインターフェイスに束縛されない => RailsはRESTなwebアプリケーションを作るためのフレームワーク

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

3. まとめ

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Thank you for listening!