フルスタックな開発環境でリリースフローを最適化してリリース効率を向上させた話
はじめに
こんにちは!J-CAT 株式会社でエンジニアをしている前田です。
私たちは、「テクノロジーとクリエイティビティで、魅力あふれる日本の姿を世界へ」をミッションに掲げている観光テックカンパニーです。弊社では、特別な感動体験に出会える予約サイト「Otonami」と、日本の魅力を世界へ届けるインバウンド向け予約サービス 「Wabunka」を運営しています。
今回は開発のリリースフローを最適化して、リリース効率を向上させた話をしたいと思います。
課題
大きく以下の二つの課題を解決した話をします。
① 冗長なリリースフロー
J-CAT では創業当初から Git Flow をベースにした開発フローが採用されており、これをずっと踏襲していました。
また、途中から Release 機能を使ったタグ付きリリースも導入されており、ある feature をリリースするまでに、「feature ブランチ →develop ブランチ →main ブランチ → リリースタグ」と、とにかくリードタイムを重視したい今のフェーズには冗長でした。
② フルスタック開発における受け入れテストの複雑性
J-CAT ではフロントエンド・バックエンド横断型の開発をしており、1 つの機能開発で双方にまたがることがよくあります。
一方でこの動作確認や受け入れテストをする手順が煩雑になりがちで、リリースのコンフリクトの原因になっていました。
「課題 ①: 冗長なリリースフロー」への対策
Git Flow ベースの開発フローから GitHub Flow ベースへ
弊社での従来の Git Flow ベースの開発フローは以下のようなものです。
Git Flow ベースだった Otonami の開発フロー
創業当初に導入されて、そのまま生き残っていたものですが、毎回「develop→main」のレビューが発生して不必要に煩雑なフローになっていました。
develop ブランチを廃止し、GitHub Flow ベースの開発フローに移行することで、開発ステップを短くしました。
GitHub Flow ベースに移行した後の Otonami の開発フロー
リリース分割の徹底
Git Flow ベースの開発を長く続けていた影響か、リリース単位が巨大になっている傾向もありました。
せっかく実装が完了した変更があっても、main にマージした後に他の機能の受け入れテストが失敗し、修正するまでリリースできない、といった場面にもよく遭遇しました。
下の図で言うと、「feature1 が main にマージされてリリース可能状態なのに、feature2 が要修正になったためにリリースが遅れる」というような状況です。
リリース単位巨大化によるコンフリクト
そこで、すべてのレポジトリにタグ付きリリースを導入し、原則「1 回のリリースに含まれるのは 1 つの PR」という粒度について徹底することにしました。
さらに、PR 単位で検証環境を立ち上げ、この環境での動作確認・受け入れテストを徹底することで、main ブランチにリリースしていない差分が含まれる時間がだいぶ短くなりました。
これらの施策により、機能同士のリリースがコンフリクトする確率が小さくなり、機能リリースのリードタイムが大きく改善しました。
リリース単位分割で機能間の依存が改善されたリリースフロー
ちなみに、J-CAT ではフロントエンドは Cloudflare Pages、バックエンドは app engine を利用しており、それぞれのプレビュー機能を使って PR 単位での環境を立ち上げています。
「課題 ②: フルスタック開発における受け入れテストの複雑性」 への対策
フロントエンド・バックエンド横断型の受け入れテスト環境の構築
J-CAT ではフロントエンド・バックエンド横断型の開発をしており、その両方に変更が加わる場合の受け入れテストは、上述のような PR 単位でのテスト手順が煩雑になってしまう課題がありました。
具体的には、以下のようにフロントエンドの PR 単位での受け入れテスト環境の接続先は、バックエンド側で main にマージするたびにデプロイされる検証環境となっていました。
結果として、フロントエンド・バックエンド一貫した開発をする際には、バックエンド側での main ブランチのコンフリクトが生じてしまい、リリース分割の徹底がしにくい状況が続いていました。
下図で言うと、「バックエンドの feature が main にマージされているが、要修正箇所が見つかってリリース可能で無いために、main に変更差分が滞留してしまっている」状況です。このような状況があると、backend の他の機能リリースとの依存関係が発生して、リリース効率が落ちてしまいます。
フルスタック開発での受け入れテストによる機能間の依存
そこで、以下のように、フロントエンドの PR で立ち上がる環境から、バックエンドの PR で立ち上がる環境に対して接続できるような仕組みを作りました。(社内では QA 環境と呼んでいます)
下図のように、フロントエンド・バックエンドともにブランチ単位で検証できるため、結合テストを機能単位で完全に独立した形で実行することができます。
改善後のリリースフロー
こちらはやや強引ですが、フロントエンド、バックエンド両方の PR タイトルに[QA-xxx-xxx-xxx]といったような特定の文字列を埋め込み、フロントエンドからの接続先を固定することで実現しています。
ここまで実現して、フルスタック開発においても機能間のリリース依存が大幅に減らせて、QA 効率・リリース効率が一気に上がりました。
結果
2024 年の 5 月, 6 月頃にこれらの改修を入れましたが、実際にどのくらい効果があったのかも計測してみました。
以下は、PR がマージされてから、Tag 付きリリースが行われるまでの実時間の平均の月別推移を示しています。
PR マージからリリースまでの時間の改善結果
当初(4 ~5 月頃)は、PR が develop に滞留することが多く、マージからリリースまで平均しても 20 時間以上かかってしいたところから、フロントエンドでは数時間以内に完了するようになりました。
バックエンドでは、フロントエンドよりかは時間がかかっていますが、それでも改善傾向が見られます。バックエンドの方が時間がかかっている理由としては、外部モジュールとの結合が PR 単位でできていない箇所も多く、main にマージしてから結合テストするケースがまだ残っているからです。(9 月はインフラがからむ案件が多く、リリースまでの時間が伸びています)
他にも定性的ではありますが、リリース作業に要するエンジニアの脳内スイッチングコストが小さくなったり、開発のスコープをより細かく分割しようという文化が定着してきたり、さまざまなメリットがあったと思います。
おわりに
これらの改善を通して、リリース時のコンフリクトの確率が小さくなり、リードタイム向上を実現することができました。
また、フロントエンド・バックエンド跨った変更に対する受け入れテストも独立して実行しやすくなったため、QA 効率・リリース効率も大幅に改善することができました。
Discussion