AppBrew Tech Blog

株式会社AppBrewの技術に関するブログ

"6年分"のRailsバージョンアップをなめらかに行う方法!

f:id:appbrew-sota:20210924144149p:plain

こんにちは、id:r7kamura です。業務委託という形で1年ほど関わりながら、美容のクチコミサービスLIPSに利用しているRuby on Rails (以下Rails) というWebアプリケーションフレームワークのバージョンを、4.2から6.1に上げました。

Rails 4.2のリリースは2014年、Rails 6.1のリリースは2020年なので、およそ6年分のバージョンアップを一気に推し進めたことになります。 今回はこれを題材に、この手のフレームワークのバージョンアップ時に起こりがちな諸問題や、やって良かったこと悪かったこと等について振り返ろうと思います。

あまりRailsに限った話はしないように心掛けて書いたので、こういったバージョンアップ作業に興味がある方にはぜひ読んでいってもらえればと思います。

変更の粒度など

今回はRails 4.2から6.1に上げる必要があったので、次のようにマイナーバージョン単位で変更を加えていくことにしました。

  1. Rails 4.2から5.0へ
  2. Rails 5.0から5.1へ
  3. Rails 5.1から5.2へ
  4. Rails 5.2から6.0へ
  5. Rails 6.0から6.1へ

まず最初に、各マイナーバージョンごとに大きなPull Requestを用意してテストを通していくことになります。実際には、最初はテストを実行できるところまでたどり着ければ御の字でしょう。 そうして出来てきた大きなPull Requestから、先に本流に取り込める変更から徐々に細かいPull Requestに切り出していき、最終的にはライブラリのバージョン変更の差分だけにして本番に反映する、というのが理想の流れです。 Gitのcommitの粒度もこれを考慮して進められると良いですね。

この辺りをさぼってバージョンアップと同時に一気に変更を取り込んでしまうと、次のように困ることがたくさんあるので、極力差分を小さくする努力をしていきます。

  • レビューの難易度が上がる
  • コンフリクトの解消に時間を取られる
  • 問題が起きたときの原因特定がしづらくなる
  • 変更を元にもどすのが大変になる

これを考慮すると、フレームワークと協調する他のライブラリの開発を行う際にも、例えば同じパッチバージョンやマイナーバージョンでRails 5.2と6.0の両方をサポートする分をリリースしてからメジャーバージョンを変えるといったように、フレームワークのバージョンアップ時に劇的な変更を求めないバージョン設計ができると良いと思います。

レビューのやり方

f:id:r7kamura:20210916074918p:plain

レビューのやり方に関してですが、数人構成のチームにレビュアーを割り当てると、その中の1人が自動的に選出されて割り当てられるという方式で進めました。GitHubの提供する機能の1つです。

業務委託という関わり方の都合上、誰がどのドメインに詳しいのかという知識が足りないことが多かったり、複数人同時に割り当てると間にボールが落ちて誰も拾わないので物事が進まないということが多かったりしますが、このやり方でそこそこ上手く解決できていたと思います。 「どうしよう割り当てられたけどRailsわからない…」と思われた方も居るかもしれませんが、社内でそれについて詳しそうな人を知っているというだけでも価値があることなので、適切な人にレビュアーを委譲してくれるだけでもありがたいことでした。

f:id:r7kamura:20210916074554p:plain

ほか個人的によくやっている運用として、個々のPull Requestに「Rails 6.0」「Rails 6.1」のようにマイナーバージョンのラベルを付けていきました。 あとで振り返ったときに掛かった工数や変更内容が分かりやすいからという理由もあるのですが、非同期で仕事を回せるようにというのが1番の理由です。 Rails 6.0の動作検証をやってもらっている間にRails 6.1のための変更をレビューしてもらったり、Rails 6.0の動作検証での問題対応用に優先して見てほしい変更を出したりと、色々なPull Requestが出ることになるので、交通整理のためにやっておいて良かったことの一つだと思います。

複数データベース対応で困った話

今回のバージョンアップで大変だった箇所のひとつが、複数のデータベースへの接続を切り替える部分の実装でした。

詳細は省略しますが、バージョンアップ前の段階で調査を進めたところ、次のような状況であることが分かりました。

  • Rails 4.2以下だけ使える既存の独自実装を現状利用している
  • Rails 4.2以上6.0以下にだけ対応しているライブラリが存在する
  • Rails 6.0以上ではRails公式の実装で対応できそう

これを踏まえて、次の手順で変更作業を入れていくことにしました。

  1. Rails 4.2の状態で、独自実装からライブラリを利用した実装に切り替える
  2. Rails 6.0まで上げる
  3. Rails 6.0の状態で、Rails公式の実装に切り替える
  4. Rails 6.1に上げる

これは今回の複数データベース対応に限った話ではないのですが、2つ以上のライブラリのバージョンを同時に変更すると何かと問題が起きがちで、原因調査の難易度も跳ね上がります。 特にフレームワークのバージョンアップでは多くのライブラリが関与することになりますが、1つを上げて様子見し、また1つを上げて…と、一度にではなく順々に進められるような適切な移行経路を見つけていくことが重要です。

今回の場合、新規に自作したものを含めて複数データベース対応用のライブラリを4つほど試したのですが、結局は移行経路の関係から上手く使えそうなものが1つだけ候補に残りました。 将来のことを予測するのは難しいですが、ライブラリ選定時はこの辺りのことも考慮しているかどうかも判断基準の一つとしたいところです。

テストがなくて困った話

テストはしっかり書きなさいよという説法的な話をしますね。

バージョンアップ時の検証はどうやっているのかという話ですが、基本的にはまずCIを利用して既存のテストを全て通します。 次に、テストで検出できないような不具合が無いか調べるために、一通り手動で検証し、洗い出された不具合に対応した上でリリースするという流れになります。

今回バージョンアップ作業を進めるにあたって、正常系のテストケースが1つでも書かれていれば手動で検証する前に検出できたのに…、と困る機会がままありました。 また、業務委託として関わりながら言語やフレームワークのバージョンアップ作業を主に担当しているという関係上、ドメイン知識に疎いところが多く、単体テストでカバーされておらず結合テストでのみ失敗するようなものが存在した場合、その原因を紐解くのに大きく時間を要することがありました。

結果的に、わからないなりに適当に正常系のテストケースを揃えていき、ついでにテスト用のフレームワークの整備もして書き方も良い形に統一していく…という作業も、当初予定には入ってなかったことですがバージョンアップの前準備として行いました。

テストは書いた方が良いものだという認識はみな何となく持っているかと思いますが、実際にこういった作業に従事してみると、改めてそのありがたみを感じます。 しかし、テストを記述するのは主に機能開発を担当している人員で、一方今回のようにテストの恩恵を受けるのは主に技術基盤の改善を担当する人員という構図になっていることから、上手くフィードバックサイクルが回っていない感覚もあります。 これはこの手の作業を分担するにあたって長らく課題に感じていることの一つで、レビュー等で意見を交わすことが妥当な落とし所だとは思うのですが、今後も上手い解決策を探していきたいと考えています。

普段テストを書く際の心構えとして、仕様を記述するためにという観点で書くのも悪くはないとは思うですが、書いている内に「これは本当に意味がある行為なのか?」と心配になることもあるのではないでしょうか。自分には覚えがあります。 その際は、ライブラリのバージョンアップ時に壊れないことを保証するために、という実際的な観点でも考えてみると、より効果的なテストが書けるようになるのではないかと思います。

急がば回れ的な改善

バージョンアップ作業だけに全力を注いで取り組むのではなく、作業環境の改善やCIの改善などにも並行して取り組んでみておいたのですが、結果的にこれで良い成果が出たと思います。こういう機会でもないとなかなか改善されない部分があったり、仕事柄ほかの開発組織でのやり方を比較的よく知っていたりという背景で、この手の改善にも着手することになりました。

小さいもので言うと、今後のバージョンアップ作業で頻繁に変更が入るであろうファイルを先にリファクタリングしておいたり、コードフォーマッターのルールを見直して近い将来発生する差分を見やすくしたり、 大きいもので言うと、完全にDockerだけで動かす開発環境を整備したり、CIの速度改善をしたりといった辺りです。結果的に作業速度の高速化に繋がり、ただ単にバージョンアップ作業だけに取り組んだ場合よりも早く作業が終わることになりました。

「バージョンアップを主にお願いしたいが、気になる部分の改善も含めてまるっとお願いしたい」と依頼されており、緩めの期限設定で信頼して自由にやらせてもらったことや、担当してもらっているエンジニアの改善への温度感が高かったこと、作業環境の制約があまりギチギチに高くはなく開発環境を整えやすかったことなどが、このへんの改善が上手くいった理由だと思います。

このアップグレードで得られたこと2つ

ここからはid:r7kamuraさんにアップグレードして頂いて特にありがたかったこと2つを、長期間バックエンドのコードに触れているid:anoworlの視点からご紹介します。

1. RailsやRubyに対し"素直な"コードになった

1つ目は「RailsやRubyに対し"素直な"コードになった」ことです。

具体的にはアップグレードに際し以下のようなことが行われました。

  • フレームワーク内部APIの不適切な使用を修正
  • 言語機能で充足できるものに対しフレームワークの機能を使っている部分の修正
  • フレームワーク機能の機能で充足できるものに対しサードパーティのgemを使用しているものの修正
  • モンキーパッチの除去
  • 不必要なデフォルト設定の変更を修正

上記が改善されることで、これらの効果がありました。

  • 開発に必要となるプロジェクト固有の前提知識が減った
  • 環境が標準化されたことから、問題に遭遇した際調べた解決策を適用できる確率が上がった

このことから、オンボーディングコストの削減や開発速度・開発体験に大きな効果がありました。

f:id:appbrew-sota:20210916153603p:plain
内部APIの不適切な使用に対する修正PR

アップグレードし言語やフレームワーク機能が増え改善が可能になったことから、今回一緒に出来て良かったと思います。

2. メンバーの言語やフレームワークの理解度が上がった

2つ目は「メンバーの言語やフレームワークの理解度が上がった」ことです。

上記の"素直な"コードになる過程でr7kamuraさんからのPull Requestをメンバーがレビューするのですが、その中には詳細に"なぜその変更に至ったか"という理由がありました。

f:id:appbrew-sota:20210916152847p:plain
PRには背景や違和感の正体などが記されている

そこはもちろんフレームワークの仕組みも絡んでくるので、レビューをする度"Railsと相性の良いコード"ひいては"フレームワークや言語、アーキテクチャと相性の良いコード"に目がいくようになりました。

これらはもちろんid:r7kamuraさんにアップグレードの折やって頂いたからこそ出来たという部分はもちろんありつつ、自分たちが同じような作業を行う時にも非常に参考になりました。

We are Hiring!

AppBrewではid:r7kamuraさんのような深い専門知識を持つ方に、機械学習、インフラ、Android、コーポレートIT、採用などでも業務委託や副業でお手伝い頂いています。

社内に無い知見をサービスに取り入れるのはもちろん、新しい視点からの突っ込みを頂くことで、メンバーの大きな刺激にもなっています。 これは会社を強くするのはもちろん、強い人と働けることは一種の福利厚生だなと個人としては思います(何処かで聞いたような……)。

そこでそんな環境で働いてみたい方、正社員・副業問わず募集しているので、お気軽に話だけ聞いてみたい方でもお声がけください〜!

herp.careers

最近出したカルチャーの記事はこちら。

type.jp