去年、Kaigi on Rails 2023にはじめて現地参加してとてもたのしかったので、今年も行ってきました。
今年の Kaigi on Railsも、一層たのしさが増していました。(語彙力)
行ってきて感じたことは、こちらの記事に書いています。
この記事には、Day 1 に聞いたセッションについて書きます。
改めて発表資料を見つつこの記事にまとめていると、曖昧だった理解が進みました。
- スポンサーセッション
- 基調講演 Rails way, or the highway(Rails の道か、さもなくば帰り道はあっちだ)
- Railsの仕組みを理解してモデルを上手に育てる
- そのカラム追加、ちょっと待って!カラム追加で増えるActiveRecordのメモリサイズ、イメージできますか?
- モノリスでも使える!OpenTelemetryでRailsアプリのパフォーマンス分析を始めてみよう
- カスタムしながら理解するGraphQL Connection
- リリース8年目のサービスの1800個のERBファイルをViewComponentに移行した方法とその結果
- Rails APIモードのためのシンプルで効果的なCSRF対策
- 現実のRuby/Railsアップグレード
- Hotwire or React? 〜Reactの録画機能をHotwireに置き換えて得られた知見〜
スポンサーセッション
現地に着くのが少し遅れてしまって、オープニングとスポンサーセッション3つあるうちの1つ目は聞くことができませんでした。
minne の Shoryuken 活用
GMOインターネットグループ(GMOペパボ)のスポンサーセッションから。
前職ご一緒していた yumu さんの Shoryuken のお話でした。間に合って良かった・・・(ゆりかもめの駅からダッシュした)
Sidekiq と Shoryuken の使い分けについてわかりやすく説明してくださっていました。
Shoryuken は、Amazon SQSをバックエンドに動きます(Sidekiq は Redis)。
スポンサーセッションですが、がっつり技術のお話でした。
基調講演 Rails way, or the highway(Rails の道か、さもなくば帰り道はあっちだ)
Palkan san
Keynote: Rails Way or the highway by Evil Martians speakerdeck.com
Rails Way の原則に沿って、どのようにアプリケーションを拡張するかについて。抽象化するコード例を示しながらの話してくれました。
Rails Wayの乗っ取って拡張されたコードが美しかったです。素晴らしかった。
(英語での講演でしたが、スライドに字幕がついていたのでどうにか理解できました)
- Rails way は Rails アプリケーションを構築する方法論、哲学
- 生産性と幸福のための最適化
- 意思決定からの解放(Omakase)
- スケーラビリティを考慮した設計
- Rails は "Hello World" から IPO までをサポートする
- 「ゼロ」から「イチ」への最適化
- 「イチ」を超えることへの課題
- Model View Controller、アプリケーションが大きくなるにつれて、3つの箱では足りなくなる
- 成長するにつれRAILS WAYから外れてくる
- おまかせは完全ではなく、埋めるべき隙間がある
- 隙間を埋めることでフレームワークを歪めてしまう 怪獣 on Rails になってしまう
- Rails フレームワークは構造を与えてくれる
- Rails Way はあなたの導きの星
- Rails Way にしたがって隙間を埋めるガイド(英語のみ)
Master Rails (Railsを習得する)
Extend Rails (Railsを拡張する)
1. Rails らしい開発者体験とコアコンポーネントとの互換性を提供する
- カスタムアプリケーションでなく、フレームワーク作者の考え方をもつ
2. 抽象化レイヤの間に明確な境界線を設ける
- Layered Auchitectureの原則
- データは上から下へ流れる、各抽象化は自分より下のものだけ知ることができる
- どこまでデータが流れるか制御することも
- それぞれのRailsの抽象化を、特定のアーキテクチャそうに対応づけられる
- Presentation / Controoer, Channels. Views
- Application / Jobs, Mailer
- Domain / Models
- Infrastructure / Adapters(DB, Mail), API clients
3. 介入より抽出 コード内で既存の抽象化を見つけよう
4. 関心事と複雑さのレベルを分離する
- 学習曲線をなだらかに保ちつつ、新しい知識を得る機会も残す
原則を実践してみる
- 複数ステップがあるウィザート方式の入力フォームの例
- 1つのフォームのために7つの新しいメソッドがあった(next_step, previous_step...)
- Controllerのback アクション、Rails らしくないアクション
- Rails は標準で複数ステップ機能を提供してくれない
- 担当分野を分離するために新しい抽象化を追加!
- Form Object の担当分野
- コンテキストよりのバリデーション
- ユーザー入力の変換
- ユーザへフィードバックを提供
- UIに基づくカスタムロジック
- ベテラン火星人開発者のコード、すごく整理されていた(スライド内のコードを参照)
class CreateForm < ApplicationForm
class Wizard < ApplicationWorkflow
- 全ての抽象化にはベースクラスが必要
- フォームオプジェクトをモデルオブジェクトの代わりにform_forで利用できる
- Controllerのコードは rails g scaffold で自動生成されたコードに似てる
- hidden_field で wizard_stateを持っていた
<%= f.hidden_filed :wizard_state %>
- フレームワークの一部となる抽象化を導入する例だった
- フレームワークと戦わなければ、Rails Way で成長することは可能
- Railsに似た抽象化を導入
- 明確な境界を引く
- 担当分野と複雑性を分離
- アプリケーションに属する
- アプリケーションのパズルとピースをうまく組み合わせましょう!
Railsの仕組みを理解してモデルを上手に育てる
- モデルを見つける、モデルを分割する良いタイミング -
五十嵐邦明 さん
Rails アプリの実装に慣れた後、メンテしやすいコードを書く技術を身につけたい人向けに、モデルを上手に見つけて、フォームオブジェクトを実装できるような知識を持ち帰ってもらうことが目標、ということでした。
最初の基調講演にも近い部分のあるお話でした。とても整理されていてためになりました。
igaigaさんの本は、Railsの道を歩くときの道しるべとなってありがたいです。
前編: モデルの見つけ方
- Railsの練習帳 -DBモデリング基礎講座 に書いてあるので読んでみてね
- テーブル設計のやり方を、ファミレスメニューを例に
イベント型モデル
- イベント型モデルは「行為を記録するモデル」、見つけるコツが必要なテーブルの1つ
- 見分け方は、名詞であるモデル名に「〜する」をつけると行為になるもの
- Order(注文)、Shipment(出荷)、Payment(支払い)
- 例)入荷処理
- 在庫を増やす(Stock)
- 支払った代金を銀行口座(BankAccount)から減らす
- StockとBankAcocuntのどちらのモデルに入荷処理を書くか迷う
- イベント型モデル「Arrival」を作る -> Arrivalモデルのreceivedメソッド
- 複数モデルで実装場所に迷った処理をイベント型モデルに書ける 🥳
- Rails wayに乗った設計方法になる
イベント型モデルに関する参考資料
PORO(Plain Old Ruby Object)を作る
- 何も継承していないただのクラス、ARを継承していない
- app/models配下におくのがおすすめ
- ActiveRecordを継承はしていないけどビジネスロジック置き場
- POROにルールを作る
- Rails wayに外れそうな境界にあるのでチームでルールを育てるのがおすすめ
- チームメンバーが同じ実装を答えられる状態にするのがルール作りの理想「迷うことを減らす」
- 例)クラス名には返すオブジェクトの名前を名詞で付ける
- もし将来、データを記録したくなったら、POROからモデルに変更しても良い
Service層を入れるのはできるだけやめてほしい
- Railsの特徴は、レイヤーを減らして書くコードの量を減らす方針(密結合な設計)
- 層を増やすのはRailsの良さを消しかねない、Service層を入れるのは相当な覚悟を持って挑む作業!!
- Service層を入れるかわりにおすすめしたいこと
- イベント型モデルを丁寧に探していく
- POROのようなシンプルな考え方から育ててく
- Service層の困りごと
- ビジネスロジックをモデルとServiceのどちらに書くか迷いやすい
- Serviceオブジェクト間でのメソッド共有が難
- Service層はみんなの理解や認識がばらばらになりがち
後編: モデルを分割するタイミング
- モデルが太っているかどう判断する?コード行数が多すぎるは気にしない
- そのまま書き続けるのがしんどくなる時 & そのタイミングで良い分割方法がある時
バリデーションを条件分岐したくなったとき
- モデルのバリデーションは、DB用とフォーム用を共有してる
- ここで Form Object!
- Form用とDB用のバリデーションをそれぞれの仕事に分離するイメージ
- 時間経過とともに良い設計が変わっていくことがある
- アプリが成長してFormとDBのバリデーションの共有がしんどくなったら分割する
フォームオブジェクトの作り方
- ActiveModel::Model, ActiveModer::Attributesを使う
- ActiveModer::Attributes
- 型を持つ attributes(カラム的なもの)を簡単に定義できるようにする
- ActiveModel::Model
- モデルのように振る舞う機能各種を使えるようになる、form_withとやりとりする機能
- ActiveModel::Model = ActiveModel:API + ActiveMode::Access
- app/forms/user_name_form.rb を作る例
- 基礎工事
- UserモデルをForm Objectへ渡す
- saveメソッドをForm Objectに実装
- initializeメソッドをForm Objectに実装
- コントローラの変更
- form_withでフォームオブジェクトを使う
- 最終形:
GitHub - igaiga/rails_form_object_sample_app
class UserNameForm include ActiveModel::Model include ActiveModel::Attributes # Userモデルに仕事を委譲するため attr_accessor :user attribute :name, :string validates :name, format: { with: /\A\p{Hiragana}+\z/ }, presence: true # Form Object から Model へattributesをセット def transfer_attributes user.name = name end def initialize(model :nil, ++attrs) # **はキーワード引数をHashで受け取る attrs.symbolize_keys! if model @user = model attrs = { name: @user.name }.merge(attrs) end super(**attrs) # 親クラスのinitializeメソッド呼び出し end # DB保存機能をsaveメソッドとして実装(モデルと似た書き味を目指す) def save transfer_attributes if valid? user.save(...) else false end end end
参考資料
- 諸橋さん 「Simplicity on Rails -- RDB, REST and Ruby」
- yasaichiさん、t-wadaさん 「texta.fm」
- yasaichiさん「Ruby on Railsの正体と向き合い方」
- 「パーフェクト Ruby on Rails 増補改訂版」
- 「Railsの練習帳」
- DBモデリング基礎講座
- https://zenn.dev/igaiga/books/rails-practice- note/viewer/rails_db_modeling_workshop
- フォームオブジェクト
- https://zenn.dev/igaiga/books/rails-practice- note/viewer/ar_form_object
- DBモデリング基礎講座
- (読みたい・・・)
そのカラム追加、ちょっと待って!カラム追加で増えるActiveRecordのメモリサイズ、イメージできますか?
integerのカラム追加で、どれだけのメモリが増えるのかということを調べた内容でした。メモリがどれだけ増えてしまうのか気になり、聞きました。
メモリがどのようなオブジェクトに増えるのか、調査していく手法にうなりました。
integerを add_columnすると何バイト増えるのか
- 224バイト、これは多い?少ない?
- オブジェクトのサイズを見るのにObjectSpace.memsize_ofを使うと、インスタンス変数までは追えない
- ObjectSpace.reachable_objecs_fromを使う
- 1階層しか辿れないので、探索していく必要がある
増えたオブジェクトを調べる
- ActiveModel::Attribute::FromDatabase
- DBからの結果をラップする
CRubyのメモリレイアウトの話
- CRubyではオブジェクトごとにRVALUEと呼ばれる固定長のメモリを割り当て(例外あり)
- Ruby3.2から埋め込み表現可能な範囲が広がっている
- Size Poolという新しい概念が増えた
- Rubyのウラガワ 笹田さんのWeb+DB Press連載記事を読み漁る
まとめ
- 224バイトというのはRVALUEが4個 ivptrでVALUE 4*2個
- ActiveModel::Attribute::FromDatabse(40+32)
- ActiveModel::Type::Integer(40+32)
- String(40)
- Range(40)
モノリスでも使える!OpenTelemetryでRailsアプリのパフォーマンス分析を始めてみよう
ymtdzzz さん
OpenTelemetryの名前はよく聞くのですが、具体的にどういうものなのか知りたかったのと、どのように Rails アプリケーションに導入すればいいのか知りたくて聞きました。
現在は数年前に比べて、OpenTelemetry の導入がしやすくなっていて、トレースによる実装見る前にひと目でボトルネックや見るべき場所が特定しやすくなるのはとても便利だと思いました。
調査の難しさ
- 調査はエンドツーエンドの粒度、ユーザーから見た遅さ・使いにくさから始まる、調査開始時点ではなにもわからない
- コードを見る前にどれだけ可能性を潰せるかが勝負
テレメトリ分析の難しさ
- ログとメトリクスで実装の粒度までブレークダウンするのは難しい
- ログ + メトリクス + トレース
- 分散トレーシングとは、1つのリクエストが、アプリケーションを構成する様々なサービスによって処理される過程を追跡する方法
- 図示されるボックスのことを「スパン」と呼んでいる
- アプリケーション側の計装(テレメトリを出力・収集できるようにすること)
- 手動計装: SpanのStart, Endを実装
- 自動計装: gemで自動出力 -> この方法でデモ
他ツールとの違い
- Profiler(rack-mini-profiler, stackprof)
- N+1 Detection(bullet)
- 分散トレーシングの強みは、テレメトリの関連付け(correlation)
- ログとの紐付け、メトリクスとの紐付け
- 1クリックで関連するログを引っ張ってこれたり
- オブザーバビリティの入り口として、トレースを紹介
- トレースが調査の入口になる(汎用的、直感的)
- 分散トレーシングは、正しく実装するのは非常に難しく時間がかかる by 入門監視
- 2024年になってどうなのよ?
分散トレーシングの導入ハードルの高さ
- 数年前まで 導入・運用コスト > メリット の感覚
- 2019.5 OpenTelemetryの登場(OTel)
- オブザーバビリティの実現に必要なほぼ全てを提供するもの
- OpenTelemetryという標準仕様の登場
- 導入・運用コスト < メリットになってきたのでは?
OpenTelemetryのはじめかた
Railsアプリケーションを計装する
- gemを入れる
opentelemetry-sdk
etc - initializerを書く
- 環境変数を設定してサーバーを起動
- デモ、架空のECサイト
- Grafannaでログやトレースの確認(コードをみる前に)
- 一目でボトルネックがわかる、トレースにはログと紐づいているので見ることができる
バックエンドの選択肢
- SaaS: NewRelic Datadog
- SaaSパブリッククラウド Amazon X-ray、etc
- OSS(自前)
- データはどのSaaSであっても一緒、データの見せ方や料金で勝負
- SaaSでの主な課金指標
- ホスト数、トレース、スパン
構成における選択肢
まとめ
- トレース
- ログ、メトリクスを補完する情報
- 関連付けにより、テレメトリ全体を強化
- OpenTelemtryにより導入ハードルが下がった
カスタムしながら理解するGraphQL Connection
yana-gi さん
元同僚の yana-gi さんの登壇でした。
新しい検索エンジンの導入をチームで進めていて、そのとき紐解いた GraphQL の知見をシェアしてくれました。
(自分が在籍していたときに、yana-giさんたちのチームは新検索エンジンの導入を進めていて、チームで色々コードリーディングなどして奮闘されているご様子だったのですが、今回、改めてその内容を知ることができてうれしかったことと、こうした発表に結実されていて本当にすごいと、発表を聞きながら胸が熱くなりました)
GraphQLとは
- クライアントが必要な情報だけ指定して取得できるデータクエリ言語 及び ランタイム
新しい検索エンジンの導入
- クライアント --GraphQL API --> minne API -- REST API --> 検索エンジン
- minne の 検索画面の API は GraphQL API
- 既存のクエリのページネーションはカーソルもオフセットも取得できる
- 新検索エンジンのAPIはREST API
- カーソルページネーションのみ
ページネーション
- オフセットページネーション
- データの開始位置を指定して取得する offset と limitで取得
SELECT * FROM products LIMIT 10 OFFSET 30;
- カーソルページネーション
- 特定のデータポイント(カーソル)を基点にデータを取得
- 時系列などの特定のデータをキーとして検索する
SELECT * FROM products WHERE id > 30 LIMIT 10;
GraphQL Connection
- GraphQLでページネーションを簡単に実装できる
- Connection: ノード(データ)をリスト形式で取得
- Edge: ノード間の関係性や付加情報
- Node: 実際のデータ本体
query { products(first: 10) { # ProductConnection Type edges { # ProductEdge Type cursor node { # Product Type }, ... }
- Connection のノード取得クエリは、開始位置を指定
team { member(first: 10, after: "abc") { nodes { id name } } }
- Connection Typeの実装
- fieldのTypeをconnection_typeに指定
- ActiveRecord Relationなど、サポートされているクラスの場合、要素を全て渡す
field :items, Types::ItemType.connection_type, null: false def products object.products # ActiveRecord Relationの場合 end
改めて今回やりたいこと
- 検索エンジンは REST API で提供されている、ページネーションは offset のみ
- 結果を取得したデータを扱うクラスは ActiveRecord ではない!つまり Connection Typeでサポートされていないクラスになりそう
- サポートされていない場合はどうすれば・・・ Custom Connection!
Custom Connection
- Custom Connectionを実装する
- (通常のConnectionでは対応できない特別なページネーションなどに対応するために使用するものらしい)
def products # ActiveRecord のリレーションを取得 relation = object.items # 作成したCustom Connection Typeのインスタンスを渡す Connections::ProductsConnection.new(relation) end
- 独自のコネクションTypeを作るには、GraphQL::Pagenation::Connectionを継承
class Connections::ProductsConnection < GraphQL::Pagination::Connection def nodes ... end def has_next_page ... end def has_previous_page ... end def cursor_for(item) ... end end
疑問に思ったこと 🤔
- Connectionのoffset(開始位置)はどうやって決まるか
真相を知るためにクエリを実行してから値が返却されるまでを追う
- Connectionのoffset(開始位置)は、ConnectionClassでcursorから計算していた
- resolverを通った後に Connection Class で offset が決まる
さらなる疑問 🤔
答え
- Promiseクラスで遅延評価を行う! 💡
まとめ
- GraphQL でページングを行うには Connectin を使う
- 独自のモデルで Connection を使うには Custom Connectin を定義する
- Connection Type Class で offset が決まる
- offset を利用するために resolver では Promise クラスを利用して遅延評価を行う必要があった
リリース8年目のサービスの1800個のERBファイルをViewComponentに移行した方法とその結果
シロクCTOの方の発表でした。スキンケアブランド N organic を販売されているサービスとのことでした。
ERBの複雑度が上がってしまって保守しにくくなるというのはあるよなあと思い、こちらの発表を聞きたいと思いました。自動生成で一気に移行したとのこと、すごいです・・!
ビューレイヤーが抱えていた課題
- 1,800以上のerbファイル
- パラメータ定義の曖昧さ、一貫性のないパラメータの渡し方、テンプレート内のロジックの多さなどによる、想定外のバグの発生
- erbには、インスタンス変数もローカル変数も渡せるので混在
- 再利用されるビュー要素はローカル変数に限定すると良い
- 複雑なロジックがテンプレート内に埋め込まれ、可読性・保守性が低下 -> 特定の条件でエラー発生
- ビュー要素のロジックをテストすることは容易ではない
- 手動テスト・実装コストの高いE2Eテスト・・・
解決策として、ViewComponent
- Railsのビュー要素を作成するためのフレームワーク
- https://viewcomponent.org/
- GitHubのエンジニアによって開発、GitHubでも利用、Reactにインスパイア
- ビュー要素ごとにクラスとテンプレートを記述、必要なパラメータを渡して、ViewComponentを描画
# app/components/message_component.rb class MessageComponent < ViewComponent::Base def initialize(name:) @name = name # 必要なパラメータを初期化引数で渡す end end # app/conponents/message_component.html.erb <h1>Hello, <%= @name %>!</h1>
<%= render(MessageComponent.new(name: "World")) %>
ViewComponent のメリット
- ロジックの分離
- テストの容易さ
- コンポーネント単位だけでなく、メソッド単位でのテストも可能
- インタフェースの明示
- コンストラクタ引数でインタフェースがわかる!
- パフォーマンスの向上
- 大量のビュー要素を表示する計測結果、ERBに比べてViewComponentはレンダリング時間が大幅に短縮(401ms -> 57ms)
どうやって移行するか?
- 移行期間中も他の開発は止められない
- 移行完了しない場合、中途半端に2つの実装が混在する
- 全てのViewComponentをerbを元に自動生成する!!!
- 移行期間中の追加・更新も自動生成を再実行すれば反映できる状態にする
- 自動生成スクリプトの実装 & 新しいビュー実装のリリース
実装
- 解析のフェーズ -> 生成のフェーズ
解析
- erb内でrenderメソッドを呼ぶ位置を決める
- 正規表現きびしかったので、Ruby Parserを使って解析
- 抽象構文木からrenderの位置を特定、抽象構文木のノードを再起的に探索してrenderを見つける
- erbの依存関係の把握
- renderを呼び出す側、呼び出される側を把握してツリーを作成
- どこからも呼ばれていないerbを削除
- ViewComponentの初期化引数に渡すパラメータを特定する必要
生成
- 解析結果を元に、rubyファイルの作成
- 必要なパラメータをキーワード引数として持ち、インスタンス変数に代入するメソッド
- erbファイルの作成
- 元のerbを持ってきて、renderメソッドをViewComponentの呼び出しに書き換え
- ローカル変数は全てインスタンス変数に書き換え
- テストの内容は、エラーはなくレンダリングができるかどうかのみ、カバレッジは目指さない
- 自動生成スクリプトとテストデータの微調整が作業工程の8割・・・大変
- これにより、PRは20万行の差分!
安全なリリース
- 段階的にリリースする方法を検討
- 別系統の生成 app/view: ERB Partials app/view2: ViewComponent
- view_pathの分岐
- Railsがビューを解決する場合、view_pathsを順に探索していくことを利用
- view_pathの操作は、append_view_pathm prepend_view_pathメソッドを呼んで ViewComponent版の実装を優先させる(app/viewよりapp/view2を優先)
- フィチャーフラグによる検証をした
- まず社内で動作を確認
- Cookieの有無でレスポンスを取得してdiffを確認
before_action -> { prepend_view_path 'app/view2' if cookies[:feature_flag] == 'view_component' }
- カナリアリリース: 一部のユーザーのみに先にリリースする
- 1%から開始してだんだん上げていく
before_action -> { prepend_view_path 'app/views2' if rand < 0.1 }
ViewComponent導入の効果
- コンポーネント呼び出しのインタフェースがわかりやすくなった
- コンストラクタを見ればわかる
- コードジャンプがしやすくなった
- クラスとしてコードジャンプが可能に
- ビューのテストが可能になり、実装ミスに気づきやすく
- 全ViewComponentのrenderのテストが可能
- 期間1年半、実装時間120時間、自動生成する戦略は成功
- パフォーマンスは、ABテストした結果、実計測での差はわずか
- ActiveRecordのクエリ呼び出し時間が支配的なため
Rails APIモードのためのシンプルで効果的なCSRF対策
corocn さん
APIモードでのCSRF対策についてのセッションでした。リーナーテクノロジーズの方で、岐阜県の nagara.rb 運営されている方の発表でした。
自分は、CSRFについてはRailsが基本的な対策をしてくれる認識でいたのですが、APIモードでどんな考慮が必要なのかなどについて知りたくてお聞きしました。
とてもまとまっていてわかりやすかったです。
RailsのCSRF対策
- Railsはトークン方式で対策、CSRFトークンを発行してチェックする
- Rails API + SPAのプロダクト開発によく出会うようになった、CSRFトークンの対策をどう組み込むのかベストなのか
- Rails wayに乗っかる場合
- ViewとControllerで数行書けば対策が終わる、初学者に優しい
- Rails API + SPA の場合、Rails wayから外れるのでトークン送受を自前で書くことになる
- トークン方式にこだわりはない、もっとシンプルに対策できない?
- 発表者の方が3年前にトークン形式でご自分で対策した記事がヒットするので、もっとシンプルな方法を伝えたかったのが今回の発表のモチベーションになったとのこと(ありがたい)
対策
具体的なCSRF対策3つ
- 基本
- リクエストの出どころを確認する Originヘッダーの確認
- より安全に
1. Originヘッダーの確認
- Originはプロトコル × ホスト × ポート番号
https://kaigionrails.org
部分 - ブラウザから飛んでくるので想定されるOriginのみ許可して開ければ良い
- なぜ既存の実装はトークンなの?Originの方がシンプルでは?
- ブラウザが Origin 送ってくれないことがあった(Form POST)、今は送られてくると思って問題ない
- 現在も送られないケースはは、GET | HEAD かつ同一オリジンのとき、GETで副作用を発生させないように作ればOK
2. Fetch Metadata
- 不正なリクエストを弾くための情報を提供するヘッダ、4種類あるが、Sec-Fetch-Siteを使うのが良い
- same-site, same-origin, cross-site, noneが取りうる値
- 基本は、same-site, same-originを見れば防御できる
- same-origin: オリジン完全一致
- same-site: サイト内(サブドメインはcross-originだがsame-site)
- Fetch Metadataの良いところは、Originの使いにくい点を解消してくれる
- わりと新しい仕様なので、ヘッダがあればチェックする方針になりそ
3. SameSite属性の指定
- Cookieの属性、クロスサイトでのCookie送信の挙動を制御するもの
- 3rd Party Cookieの制御のために作られた、CSRF対策で使えるのはおまけ、広告・プライバシー文脈
- SameSite属性の種類 -> Lax or Strictを指定すれば良い
- 2分ルールがある
- 現行ブラウザはすべて対応しているが、デフォルトNoneのブラウザがあるので、明示的なLaxの指定が必要
Railsでの実装
- SameSite指定は、config.action_dispatch.cookies_same_site_protection、6.1以降はデフォルトLaxなので指定不要
まとめ
現実のRuby/Railsアップグレード
Ruby、Railsのアップグレードを多く経験された発表者の方が、アップグレードの進め方、発生した問題、アップグレードし続けるために必要なことを実体験を基に発表してくれました。
現実問題にまみれながら、Ruby/Railsアップグレードを地道に進めていかれている様子に感動を覚えました。特に、リソースの確保と、エンジニアの説明責任の話が刺さりました。「余裕ができたらやる」は永遠にやらない、本当にそれはそう・・・。
当時の課題
アップグレードのための準備
- RubyとRailsをセットでデプロイできるインフラ
- テストの整備と、テストを根付かせるしくみづくり
- エラーを通知するしくみづくり、Sentry通知
- 非推奨警告をSentryに記録、将来壊れることがわかっているので原則すぐ対処、見つけ次第殺していきましょう
- ビジネス側との関係を密にし理解を得る
アップグレードの手順
段階的なアップグレード Rails1
- Railsガイドのアップグレードの通りに進めましょう
- bundle update rails、依存関係を確認しながら足していく、ここでコミット
- bundle exec rails app:updateで、application.rbなどフレームワークが作るファイルの作り直し
- 全て上書きでOK
- テスト実行、必ず失敗する
- エラー・警告を消す、コミット(gemもなぜ失敗したか確認する)
- 運用に回して様子を見る
段階的なアップグレード Rails2
- アップグレード後、しばらく運用してからフレームワークのデフォルト設定に対応
Rubyのバージョンアップ
発生した問題とその解決
- 保守されていないgem
- formして自分で保守、別のgemに置き換える
- Railsをモンキーパッチしているgem
- 使わない、動いているうちにやめる努力、入れる前にコードに目を通しておく
- Rubyの非互換な変更(文法の変更、キーワード引数の変更など)
- 警告を有効にする、新しいRubyを想定したgemにアップグレード、テストコード
- 標準ライブラリ仕様変更でOpenSSL周りが壊れたので、テスト書いてよかったと思った
- Railsの非互換な変更
得られたもの
- 書き味の向上、デバッグgemが進化した
- 脆弱性修正がされるバージョンになって安心 Apache log4jとか
- パフォーマンス向上
- Rails7.2よりRuby3.3でYJITに
- テスト書く文化
- 業務領域に対する理解、アップグレードのための調査でコードリード
- ビジネス側からの信頼
アップグレードしていくために
- gemの選び方
- コードを眺めて、保守が止まっても自分が保守できるかをみる
- 代わりのgemと切り替えられるようなインタフェースになっているか
- 特定のgem依存をなるべく局所化したい
- コードの書き方、賢そうなコードを書かない、依存関係を閉じ込める
- 日々こつこつやっていきましょう
- dependabotで月1アップグレードするのを自動化して面倒にならないように
- リソースの確保が必要
- 余裕ができたらやるは永遠にやらない、計画に入れる
- エンジニアの説明責任
- アップグレードはコストはかかるが、やらないリスクは確実にある
- 信頼して説明を聞いてもらえるように努める
Hotwire or React? 〜Reactの録画機能をHotwireに置き換えて得られた知見〜
フィヨルドブートキャンプ出身の方の発表でした。
ある機能を React から Hotwire に置き換えた経験を通して、Hotwire と React のどちらを選択するか考える基準を示してくださった発表でした。
プロポーザルを提出した時点からさらに深掘りして得た知見を共有してくださっていて、素晴らしかったです。
このくらいの規模のサービスに本当にReactは必要?
- 英語塾キャタルに通う生徒に提供している小規模なアプリケーション、部分的に React で SPA を実現
- やりたいことはHotwireで十分できるのでは?でもHotwireに移行するのはちょっと怖い
- Stimulus の情報があまりなくて未知数、技術検証する価値はありそう!
- 録画機能を置き換えてみる
Hotwire/Stimulusとは?
- Turbo と相性の良い JavaScript フレームワーク
- Stimulus自体に書きづらさはない、でもしっくりこない
- ごりごりにJS書くことになってしまう
- プロポーザル提出時の結論
- Stimulusのコントローラに過度なJSロジックが集中、それならReactが良いよね、となった
- 書きづらさがない、の正体は
- React HooksなどReactに関する知識が必要ないが、その分、JSのコードを書く必要があった
- 一方、Turboはチームで好評
- 仮説1: HotwireのうまみはTurboなのでは?
- Turboに寄せて、JSを減らせるのでは
- Hotwier/Turboは、CRUD操作を中心としたSPAをJSを書かずに実現できる
- Yasuko Ohvaさんの去年の Kaigi on Railsでの発表が大きなヒントに
- SPAとHotwireでは、アプリを構成するためのものの考え方が大きく違う
- 画面の流れに沿って実装するのはだめだった
- CRUD操作を基点に切り替えるところはTurboを利用できそう
- 「紙芝居の仕掛け」として捉える
- PRはマージされた
Hotwire or React?
- CRUD操作が主体 + 補助的なJSが必要な画面
- 紙芝居 + ちょっとした仕掛けがある場合、Hotwireで十分対応できる
- CRUD操作を伴わずリッチなインタラクションが必要な画面
- Reactの得意分野
- CRUDを含む高度なSPA実装が必要な画面、CRUDなしでシンプルなSPA実装が必要な画面
- 意見が分かれる
- React と Hotwire 、どちらも一長一短
- Hotwireで実装できそうか?とい視点の場合、実装をCRUDに集約できるかどうかが重要
- Hotwire的な設計ができれば適用可能な範囲は広がる
このあとは懇親会をたのしみました。
また2日目の聞いた内容もレポートを書きたいと思います。