見出し画像

レガシーアプリケーションのRubyやRailsを最新に保ち続ける技術

 くふうAIスタジオでトクバイ等買い物事業の開発基盤を担当している根岸(@negipo)です。宮本フレデリカさんお誕生日おめでとうございます。

 くふうAIスタジオが運営するトクバイのバックエンドを構成するいくつかのサービスのうち、メインモノリスはモデル数が1000を大幅に超える規模のRailsアプリケーションです。元を辿れば15年近い歴史を持っており、関連ソフトウェアのアップデートが数年間停滞してきた結果、Ruby2.3/Rails5.0というEOLをかなり過ぎた状態で2022年まで運用されていました。また他の主要なRailsアプリケーションもほとんど同様の状態が続いていました。2022年以降、そのような状況を踏まえて、くふうAIスタジオの開発基盤チームはRuby/Rails等依存のアップデートに一定の注力を行っており、現在では主要なレポジトリのほぼすべてにおいて最新の安定版であるRuby3.3/Rails7.1で運用しています。また、ほかのほとんどの依存ライブラリも最新の状態を継続的に保つことができています。

 本稿では、なぜ、どのようにしてこのアップデートを行なったのかを、技術面以外の観点も大きく踏まえて解説していきます。

意思決定

 数年レベルでアップデートが放置されレガシーとなった巨大なアプリケーションに対応するには、個人レベルの努力では難しく、組織的な意思決定が必須です。トクバイでも実際に数年に渡って何度か小規模なアップデートプロジェクトが立ち上がっては消えていきました。

 まず、組織において理想と現実に大きな差があると認識し解決しようとする場合、専任のチームを作ってさまざまなリソースを与えることが必要です。トクバイを運営する買い物事業では根岸が2022年にバックエンド担当として開発基盤チームに組み入れられ、これまでほとんど無視されていた品質特性の向上タスクにリソースが割かれるようになりました。

 当初火を見るよりもあきらかに最も悲惨な状態だったCI/CDの改善プロジェクトが終わったあと、チームは次に解決するべき課題を探すために『バックエンドアーキテクチャ健全性アンケート』を作成してバックエンドの開発者全員に対し調査を行いました。これは変更容易性や再利用性等の品質特性に対してそれぞれ『期待度』と『達成度』を四件法で評価し、期待度から達成度を引いた統計値を『未達度』として評価することで、どのような品質特性に開発基盤チームの注意を向けるべきかを調査することを目的としていました。

調査内容
調査結果


 内容や解釈等、詳細については割愛します。このような調査の一方で、トクバイの依存しているソフトウェアが古いことを理由にライブラリを利用できず、最新のソリューションを選択できないという事例もよく知られていました。これらをもとにチームはトクバイの各アプリケーションの最新に追随していないRuby/Rails等の関連ソフトウェアのバージョンアップを最も重要なタスクと確信することができ、また組織的な承認も得ることができました。

調整と信頼構築

 ソフトウェア・アプリケーションを含め、いかなる道具も社会的に成立しています。あるアプリケーションを更新したい場合、その機能が果たす役割を損なわないよう、様々な側面からアプリケーションの価値を十分に理解することが必要です。そうしたアプリケーションのドメインには、ビジネス的な役割や社内外のユーザーやステークホルダー、またネットワークインターフェースによって連携する別のアプリケーション等、確認すべき項目が多岐に渡ります。また逆に、ソフトウェアのアップデートは技術者から見れば当然必要かつ重大ですが、実際にアプリケーションに直接触れる人々にとっては必ずしもそうとは限らないため、アップデート作業の価値についてチーム外に十分な理解を得る努力をする必要があります。

 まず、適切な社内のステークホルダーを見つけてヒアリングの場を設けましょう。対話をもとに、必要な観点からアプリケーションの重要な機能を整理し、また信頼関係を構築しましょう。

アプリケーションのビジネス上の役割を整理し、重要な機能を要求としてまとめ、検証項目とする

 ヒアリングを元にアプリケーションについて十分に理解したら、そのアプリケーションが果たしている役割を構成する機能について検証項目を作成し、リリース前後に各環境でアップデートを行う開発者自身が検証するようにして、レイヤーのもっとも高い一級のテストとして取り扱いましょう。アプリケーションの詳細な機能に個別のユニットテストが書かれていることは重要ですが、そのアプリケーションが依存の海でいっぱいの現実の環境であるべき要求を満たしているか検証することも同様に重要です。

 UIではなく、同期・非同期問わずネットワークインターフェースを持つような実装のアップデートを行うこともあるでしょう。このようなアプリケーションではテストケースをコード化してしまうことを積極的に行っても構いません。とにかく、リリース前後に各環境で実行する検証をチェックリスト化しておきましょう。

メリットとともに大きなリスクがあることを十分説明し共感してもらう

 ソフトウェアのアップデートには品質を改善するという目的がありますが、その裏で障害を発生させうるというリスクがあります。このリスクとリターンの関係について、事前に十分な理解を関係するステークホルダーに持ってもらうことは、今後の協力を得る上で非常に重要です。問題が起きた際に計画的に対処できるよう、包み隠さずリスクについて話し、評価してもらうようにしましょう。

 もしビジネス上看過できないブロッカーがある場合には、うまく回避できるように相談しましょう。外部契約上重要なタイミングや、開発が活発な時期は避けるようにしましょう。必要があればエラーバジェット等を導入し、アップデートによるリスクが低くなる様々なタスクを優先する等、アプリケーションに対する要求を破壊しないよう積極的に行動しましょう。

リリースや問題発生時のコミュニケーションパスを設計して合意する

 アップデートのリリースによってリスクが高まることはすでに説明しました。そのため、リリースがいつ行われるか、行われたのかを重要なステークホルダーに知られるようにすることは重要です。また、アップデートに伴う障害が発生した際にコミュニケーション上の問題が起きないよう、どのようにステークホルダーに伝えるかは事前に決定しておく必要があります。

 もしチャットアプリケーションを使っていたら、通常のコミュニケーションや、アップデートや障害等のイベント発生時、どのチャンネルで誰にどのように伝えるかをステークホルダーと合意しておきましょう。こうしたコミュニケーションパスに事前の合意を作ることで、コミュニケーション上の不安を少なくし、発生しうる信頼関係上の課題をコントロールすることができます。

他のステークホルダーを整理し紹介してもらう

 ヒアリングを行うことで、さらにあらたなヒアリング対象となる社内外のステークホルダーが見つかるかもしれません。アプリケーションには人間と同じように異なるいくつもの側面があります。それぞれの社会的役割についてそれぞれ適切なステークホルダーに詳細を聞き、アプリケーションの複雑な存在をなるべく正しく理解しましょう。

インプット

 もしRuby/Railsアプリケーションに対する十分に長い利用実績があるとしても、アップデートを実行する個人はライブラリをアップデートするというタスク自体が初めてかも知れません。Ruby/Railsをアップデートする前に、何をするべきなのか、どのようなリスクがあるのかを知るために、十分な情報を集めることは非常に重要です。

 また、Ruby/Railsを取り巻くコミュニティは巨大なため、公式以外にも組織や個人においてアップデートの知見を詳述しているブログは多いです。なるべくたくさんの信頼できる情報元を見つけて、事前に読み込み、整理しておきましょう。

 アップデートにはそれを補助する適切なツールがあります。Railsのアップデート時に公式が提供している`bin/rails app:update`を実行したり、公式の機能で事前に機能廃止に対する警告にうまく対応したりすること等は当然その一部ですが、Next RailsDeprecation Toolkit等によるトラッキングもアップデートのリスクを押し下げてくれます。こう言った外部のツールを知っておくと良いでしょう。

健全性の担保

 アップデートを行う前にアプリケーションの一般的な健全性を可能な範囲で上げておきます。Ruby/Railsが古い状態では様々な依存ツールも古いものしか使えない状態なので苦労するかもしれませんが、この段階に力を注がないとアップデートに伴うタスクの生産性を悪化させてしまいます。

GitHubやCircleCI等の設定の確認

 まずバージョン管理やCI/CD関連サービスであるGitHubやCircleCI等の設定を見直し、標準的なプラクティスからひどく離れた設定になっていないか確認しましょう。たとえばmainブランチのプロテクションやCircleCIのAWSクレデンシャルのOIDC対応等、組織内の標準を適用することで細かな問題の発生を未然に防ぐことができます。

テストカバレッジ

 テストカバレッジの指標や目標値の意味については色々な解釈がありますが、トクバイではsimplecovによるLine Coverageの計測で60%を閾値とし、これを下回った場合にはアップデート前に分析を行って改善作業をすることを最低限のワークフローとしています。レガシーなレポジトリはFat Controllerになっていることが多いため、Controllerのカバレッジには特に注意します。またカバレッジが非常に低い場合には、一般的なテストプラクティスからは離れ、サービスクラス等をモックせずに統合テスト的なテストを作成し、ユニットテストの異常検出時の対応の容易さやテストパフォーマンスよりもクラス間の包括的なインターフェースの健全性やテストの書きやすさを取ることをトレードオフとして意識することもあります。同様の理由でフロントエンドの動作を担保するためにcapybaraselenium-webdriverを活用したSystem Testは積極的に新規に導入します。

 テストを書くときにはGitHub CopilotやChatGPT等の生成AIを多用しましょう。実装を与えれば瞬時に9割がた正解のテストを書いてくれます。利用しているgem等のコンテキストを与えれば、以降は半自動で大量のテストを記述することができるでしょう。生成AIを業務利用するための条件が整っていなければ、積極的に組織に働きかけて実現しましょう。

開発環境の整備

 対象となるアプリケーションは積極的な開発がなされていない保守フェーズに入っているかもしれません。そうした場合、開発環境にコンテナ技術を導入しない理由は少なくなります。積極的に開発環境のDockerコンテナ化を検討しましょう。キャッシュ等を利用して、ビルドに時間がかからないよう工夫します。同時に、開発環境に必要な依存を開発者のマシンでセットアップするようなツールからは古い依存を削除していきます。

CI/CDの整備

 CI/CDを整備することは開発生産性上非常に重要です。一方で、古いアプリケーションでは開発者のローカル環境からビルドやデプロイをすることがワークフローになっているかもしれません。GitHub Actions、CircleCIやCodeBuild/CodeDeploy等を利用して、ビルドやテスト、デプロイが極力自動的に行われるように整備しましょう。自動テストの実行等に開発環境の整備で作ったコンテナイメージが役に立つかもしれません。

 あまりないことですが、自動ビルドが整備されていない環境に対し、フィルターを使って予算上の課題を解決しながら自動的にビルドが実行されるように工夫することもできます。トクバイのメインモノリスアプリケーションではコストの関係上テストやステージング環境の準備は手動(CircleCIワークフロー上でApprove時のみ実行)で行われていますが、Ruby/Railsアップデートを実行していて頻繁に検証が必要なブランチに対するビルドだけ自動実行することで、全体としてコストの発生をうまく抑えつつ利便性を上げています。

アップデートの実行

 Railsアプリの場合はRuby/Railsが主要なアップデート対象となりますが、Ruby/Railsがともに非常に古い場合、RailsのRubyに対する依存バージョンを確認し、Ruby/Railsをそれぞれどういう順番でどのバージョンに上げていくかを計画しましょう。作った各バージョンのテーブルを、はしごを登るようなイメージで、バージョンアップラダーとして定義しましょう。

バージョンアップラダーのステップ

 各バージョンアップでは、まず条件に合致するよう依存ライブラリを更新していきましょう。トクバイのアップデートは当初`bundle update --conservative {gemname}`で最小限の保守的なアップデートを行っていましたが、間接依存やパッチレベルアップデートは思ったよりも実装が壊れることが少なく、次第に時間効率を優先して大抵のケースで`bundle update {gemname}`していくことになりました。そうしてライブラリのアップデートが必要なことがわかったら、該当するライブラリのchangelogやアップグレードガイドを確認しましょう。セマンティックバージョニングに従って、メジャー・マイナーバージョンアップの差分はより注意して確認しましょう。

 ライブラリに必要な変更にIssueやPull Requestが提出されていない状況を見つけたら積極的にコントリビュートしましょう。また、不要そうな依存は積極的に外していきましょう。保守が長期間滞っているものには特に注意を向けます。しかし、一般に広く使われている代替ライブラリが存在しない、なかなか変更がリリースされない等の理由で、通常の依存の指定や削除では問題が解決できなくなるかもしれません。そういった場合には、ライブラリのforkや、Gemfile上の`github:`表記で自分が管理しているレポジトリを指定する、モンキーパッチを実装する等で一時的に凌ぐ等の対策を行って、ひとまずアップデートを前に進めましょう。そうした一時しのぎをアプリケーションに入れたあと、時間とともにコミュニティが問題を解決してくれることもあります。

 Deprecation Warningを管理するツールがあることはすでに述べました。Next RailsDeprecation Toolkitを利用して、問題のある依存ライブラリや実装を検出しましょう

 別のサービスやプロセスとの通信を管理したり、その内容となるフォーマットを表現するためのライブラリをアップデートする場合は特に注意が必要です。ライブラリと共にサービスも非常に古い場合があるため、ライブラリのみをアップデートすると問題が起きる可能性があるからです。得てしてそういったサービスはテストではモックされていることが多いため、より後の段階になって問題が発覚しがちです。可能な範囲で事前にライブラリとサービス間の合致するバージョンテーブルを確認し、必要であればサービス側のバージョンアップを検討する必要があるでしょう。また、バージョンアップ後も機能が動作しているか十分に検証しましょう。トクバイのアップデートでは、ElasticSearchと通信するelasticsearch-railsとその周辺のgemや、Fluentdと通信するfluent-loggermsgpack等が該当しました。

 バージョンアップに従って実行するべき内容は細かく細分化できます。チェックリストを作って少しずつ進めると良いでしょう

  • `bin/rails app:update`を実行し、適用する内容を取捨選択する

  • 開発環境のサーバを立ち上げる。テストを実行する

  • CI上でテストを実行する

  • ステージング環境をビルドし立ち上げる

  • 本番環境をビルドする

 本番環境へリリースする前の実装フェーズでできることが終わったら一旦区切りをつけましょう。mainブランチの古いRuby/Railsアプリケーションに即反映できるコミットを選別し、アップデートよりも前に反映してしまいましょう。Ruby/Railsのバージョンアップラダーを一段登る時の差分のサイズを小さくすることで、問題が起きるリスクを下げることができます。

検証

 『調整と信頼構築』で言及したように、チームは重要な機能をステークホルダーからヒアリングしています。この内容をそのまま検証項目とし、アップデートの事前事後でステージング環境・本番環境で確認するようにしましょう。また、検証項目をドキュメント化し、将来チーム内外で必要になったときに検証を再現できるよう気を配ることは、今後の開発に役立つでしょう。

 ビジネス要件が重い場合、外部のチームの協力が重要になることがあります。トクバイでは以前から主にモバイルアプリケーションの検証を目的としたQAチームが組成されており、協力を仰ぎながら社内のリソースを使った検証体勢を整備して検証フェーズを乗り切ることができました。またその次の段階として外部のリソース(QAを専門とした協力会社)に発注を行うことで、長期的にコストを最適化することができています。アップデート全体を前に進めるために組織的な協力体制とリソースの準備が必要だということが、ここでも示されています。

リリース

 Ruby/Railsのアップデートのようなリスクのあるリリースを行うときには、できる限り小さくリリースし、リスクを最小限に保ちましょう。トクバイではカナリアリリースを全体リリースの前段階としておこない、スタッフや10%のユーザにのみアップデート後の実装を晒すことで、本番環境における問題解決をより小さい影響範囲のもとで行っています。環境による設定値や利用しているサービス、稼働している機能等の差異や、そのたの品質要件等、テスト環境やステージング環境では表出しないアップデートによる問題を全体リリースの前に収集し、解決することができます。

すぐにアップデートをリリースできないとき

 ビジネス上の要件で検証フェーズが重い等の理由で、作ったアップデート差分のリリースが即できず、Pull Requestを保守し続けなければならないことがあります。トクバイのメインモノリスの場合では、実際にPRのopenからマージまで半年以上かかったケースがありました。このような場合には以下のようなプラクティスが参考になるかもしれません。

 それぞれのアップデートによるバージョンアップラダーのステップにおいて、実装上の差分は複数のPRにマッピングされると思います。Git管理上、アップデート全体を通して、うまくそれぞれのPRが事前ステップのPRをベースブランチとするように積まれ、かつ最新のmainブランチから大きく乖離しないよう継続的に管理しましょう。`git cherry-pick`や`git rebase --onto`等をうまく利用したスクリプトを書くことでブランチ全体の保守がやりやすくなります。

 アップデートが長期化する場合、特にGemfileやGemfile.lockのコンフリクトの管理が難しくなっていくことが予想されます。トクバイではGemfileやGemfile.lockをRubyバージョンごとに作成して先にmainブランチに入れておき、開発者がライブラリを追加・更新する際に全バージョンに対して変更可能にすることで、先々のバージョンでmainブランチで利用されている依存ライブラリのバージョンとの乖離が生まれることを避けていました。ここに挙げたような工夫が必要になる状況は多くはないと思いますが、長期に渡ってPRを保守するようなことがあれば、なるべくチーム外とのコンフリクトの解決と最新の変更への追随にかかる労力を最小化できるよう、実装や開発スタイルの特徴に合わせた対応が行えると、本当に必要な作業により集中できると思います。

アップデート後の改善

 長くバージョンの固定されたアプリケーションを触り続けてきた開発者は、古い実装手法が習慣化してしまい、Ruby/Railsの(数年前の)新機能があらたに使えるようになってもなかなか手を伸ばしづらいことがあるかもしれません。アップデートを行った際にどのような変更があったのかを一度見直すことで記憶をリフレッシュし、新機能を積極的に利用できるようにすることは生産性上重要です。トクバイでは開発基盤チームはRuby/Railsアップデートのたびに社内向けの情報共有ツールにアップデート情報を詳述し、あたらしい機能や廃止された機能について紹介することで、アップデート後の環境に開発者がすんなり馴染めるよう努めています。

 このような環境下で静的解析の果たす役割は重要です。RuboCoprubocop-railsによって、あたらしい言語の特徴やフレームワーク機能による標準的なプラクティスをすんなりチームに導入することができます。reviewdog等のツールを用いて、GitHub上のレビュープロセスに静的解析を組み込むことを検討してください。静的解析に対する違反の可視化や、ルールそのものの議論の昇華をうまく行うことができるでしょう。また、アップデートがしばらくマージできないようなケースでは、先々のRuby/Railsバージョンにおいて廃止されるような禁止したい実装を静的解析を用いて違反化することができます。rubocopのカスタムコップや、もっと単純にテストと正規表現を組み合わせるだけでもかなり有効なので、手法をよく検討すると良いでしょう。

 Ruby/Railsに限らず、依存のアップデートは継続的に行われる作業ですが、アップデートを通知して差分の作成を行うような自動依存管理ツールにDependabotRenovate等が挙げられます。トクバイでは設定項目の多さ等からRenovateを選定していますが、チームにあったツールを選定し、運用すると良いでしょう。依存ライブラリが網羅的に常に最新になっている状態では、数年放置されたアプリケーションよりもはるかにRubyやRailsのアップデートが楽になります。また、ここで挙げたようなツールの設定や詳細を隠蔽したCircleCI Orb等を、社内向けに標準化されたボイラープレートレポジトリとしてまとめることで、新規のRuby/Railsアプリケーションが作られたときに開発者が参照し、問題が発生しにくくなるよう情報の共有がなされています。

まとめ

 数年の間放置された巨大なレガシーアプリケーションのRuby/Railsアップデートにどう対応するかについて書きました。動き出しこそ技術的にも組織的にも様々な対応が必要ですが、ここで挙げたような基盤の整備を行うことでアップデートは習慣と化していきます。最新の依存ライブラリやプログラミング言語に追随することはデイリータスクになっていき、アプリケーション運営上の最も大きな課題ではなくなるでしょう。

 そうしているうちに、専門のチームはアップデートに必要なくなっていきます。組織上アプリケーションの開発や管理を進めている適切なチームがあれば、自動依存管理ツールによるアップデート等については委譲してしまいましょう。アプリケーションの機能は日頃開発で馴染んでいるチームの開発者がうまく把握しているものですし、横断的なチームよりも適切な注意を払ってアップデートできる場合もあるでしょう。何より組織のスケールのためにはこのような委譲は必須です。

 ソフトウェアのバージョンが古いことは、ソフトウェア・エンジニアの孤立を招きます。依存の古いレガシーアプリケーションを運用している人は、たとえ技術カンファレンスでプログラミング言語の最新機能を知ったとしても、業務でそれを適用できる機会はありません。自分だけではどうにもできないような状況でそのようなことが何度も起きれば、そのソフトウェア・エンジニアは自分の活動が社会から阻害されていると感じるかもしれません。

 利用しているソフトウェアのバージョンを最新に追随させ続けることほど、万人にわかりやすく品質上の価値が高い習慣はほかにありません。もしあなたがその担当者であるならば、自分がやろうとしていることの価値に確信を持てるよう努力してください。もしあなたの周囲でレガシーアプリケーションのアップデートを進めようとしている人がいたら、ぜひ手を差し伸べて協力してあげてください。健全なアプリケーションを作る努力のその先に、ユーザーにとってより価値の高いアプリケーションを作り続ける、素晴らしい生産性を持った開発チームが輝いているでしょう。

くふうAIスタジオでは、採用活動を行っています。

当社は「AX で 暮らしに ひらめきを」をビジョンに、2023年7月に設立されました。
(AX=AI eXperience(UI/UX における AI/AX)とAI Transformation(DX におけるAX)の意味を持つ当社が唱えた造語)
くふうカンパニーグループのサービスの企画開発運用を主な事業とし、非エンジニアさえも当たり前にAIを使いこなせるよう、積極的なAI利活用を推進しています。
(サービスの一例:累計DL数1,000万以上の家計簿アプリ「Zaim」、月間利用者数1,600万人のチラシアプリ「トクバイ」等)
AXを活用した未来を一緒に作っていく仲間を募集中です。
ご興味がございましたら、以下からカジュアル面談のお申込みやご応募等お気軽にお問合せください。
https://open.talentio.com/r/1/c/kufu-ai-studio/homes/3849

ハッシュタグ

#中途採用 #エンジニア採用 #採用広報 #株式会社くふうAIスタジオ #テックブログ