Slide 1

Slide 1 text

フロントエンドの Monorepo をやめて リポジトリ分割したワケ Hack@DELTA v24.06

Slide 2

Slide 2 text

自己紹介 株式会社カミナシ ソフトウェアエンジニア 坂井 学 (Manabu Sakai) 略歴 2016 年に freee 株式会社に入社。 SRE チームでクラウド最適化やマ ルチプロダクトを支える仕組みづくりに取り組む。 もう一度 1 → 10 フェーズのサービス開発をやりたくて、2022 年に 株式会社カミナシへ入社。 BtoB SaaS は 3 社目でかれこれ 10 年以上の関わり。 カミナシでの仕事 主力サービスである「カミナシレポート」のサービス開発全般

Slide 3

Slide 3 text

はじめに 今回の発表内容は「カミナシ エンジニアブログ」に書いた記事をベースにしています。 リポジトリ分割を行なって 1 年ほど経過したので、当時は書けなかったその後の話もお話しできれ ばと思っています。 https://kaminashi-developer.hatenablog.jp/entry/2023/05/22/goodbye-monorepo なお、カミナシでは Monorepo を否定しているわけではなく、Monorepo を採用しているチームも あります(それぞれのサービス開発チームがオーナーシップを持って意思決定しています)。 これ以降の話は、私が担当している「カミナシレポート」というサービスの話です。

Slide 4

Slide 4 text

2 つのフロントエンド カミナシレポートにおけるフロントエンド カミナシレポートには、2 つのフロントエンドが存在します。 ● 管理者の方が使う Web アプリ (web) : React ● 現場の方が使うモバイルアプリ (mobile) : React Native + Expo SDK どちらも Node.js と TypeScript を採用していることもあり、以前は Yarn Workspaces を使った Monorepo で運用されていました。

Slide 5

Slide 5 text

harami_client ├── node_modules ├── package.json ├── packages │ ├── api_docs 👈 OpenAPI 定義 │ │ └── package.json │ ├── mobile 👈 React Native + Expo SDK │ │ └── package.json │ └── web 👈 React │ └── package.json └── yarn.lock リポジトリ構成 node_modules はルートに配置されていて、各アプリケーションからは hoisting されて参照されて います。 harami はコード ネームです 🥩

Slide 6

Slide 6 text

カミナシの Monorepo における課題

Slide 7

Slide 7 text

カミナシの Monorepo における課題 ① Monorepo ゆえの密な依存関係 ある時 Expo SDK をアップグレードするために mobile 側を React v18 に上げる必要性が出てきま した。しかし、ここで大きな問題が発生します。 ● Expo SDK は React Native と React のバージョンに強く依存 ● React のバージョンは web と mobile で揃える必要がある ○ ルートの node_modules を共有しているため このことから web 側も同時に React v18 に上げる必要がありましたが、同時に進めれるほどの人 数はおらず後回しに…。

Slide 8

Slide 8 text

カミナシの Monorepo における課題 ① Monorepo ゆえの密な依存関係 あるパッケージで利用しているライブラリを別のパッケージから利用できてしまうため、暗黙的な 依存関係も生まれてしまっていました。 web への変更が mobile に影響を与えてしまい(逆も然り)、不具合を発生させてしまったことも ありました。残念ながら適切に依存関係をコントロールできておらず、密な依存関係がデメリット になっていました。

Slide 9

Slide 9 text

カミナシの Monorepo における課題 ② リリースの分離 暗黙的な依存が生まれてしまっていたことから、web と mobile のどちらかだけの変更であっても 両方リリースする運用になっていました。 独立してより細かいサイクルでリリースするためにも、暗黙的な依存を絶ってリリースを分離させ る必要がありました。

Slide 10

Slide 10 text

カミナシの Monorepo における課題 ③ アプリケーションのライフサイクルの違い Expo SDK はおよそ 3 か月おきにメジャーリリースが行われますが、最新を含めた 2 つのバージョ ンしかサポートされず、それより古いものは Deprecated になります。 そのため常に最新バージョンを追いかけ続けることが重要ですが、お互いが密に依存している状況 だと対応コストが増えてしまい、通常の機能開発を圧迫してしまっていました。

Slide 11

Slide 11 text

カミナシの Monorepo における課題 Monorepo では解決できないと判断 フロントエンドに詳しいエンジニアを中心に Monorepo のまま上記の課題を解決する方法がないか 検討を行いましたが、最終的にはリポジトリ分割を行うことが最も妥当であるという結論に辿り着 きました。

Slide 12

Slide 12 text

補足 独立型と集中型 Monorepo で異なるバージョンの React を使う方法 で紹介されているような node_modules を共 有しない独立型にすれば、Monorepo 構成を維持できたかもしれません(以下、記事より引用)。 ● 集中型 Monorepo: ルートにすべてのライブラリをインストールして、それぞれのアプリケーションやモジュールで利用 ○ すべてのライブラリがルートの package.json に記述される ○ Monorepo 全体で同じバージョンのライブラリが利用される ● 独立型 Monorepo: それぞれのアプリケーションやモジュールで必要なものをそれぞれでインストールする ○ それぞれのアプリケーション・モジュールが使うライブラリがそれぞれの package.json に記述される ○ それぞれのアプリケーション・モジュールで異なるバージョンのライブラリが利用できる

Slide 13

Slide 13 text

リポジトリ分割のステップ

Slide 14

Slide 14 text

リポジトリ分割のステップ OpenAPI 定義の移行 1 OpenAPI の npm パッケージ化 2 ソースコードの 分離 3 元リポジトリの リネーム 4

Slide 15

Slide 15 text

リポジトリ構成(再掲) node_modules はルートに配置されていて、各アプリケーションからは hoisting されて参照されて います。 harami_client ├── node_modules ├── package.json ├── packages │ ├── api_docs 👈 OpenAPI 定義 │ │ └── package.json │ ├── mobile 👈 React Native + Expo SDK │ │ └── package.json │ └── web 👈 React │ └── package.json └── yarn.lock

Slide 16

Slide 16 text

リポジトリ分割のステップ ① OpenAPI 定義の移行 packages 配下に web と mobile のソースコードがありますが、当時は OpenAPI 定義もクライア ント側のリポジトリに存在していました(クライアント側でしか OpenAPI 定義を使っていなかっ たため)。 リポジトリ分割の前に、まずはこの OpenAPI 定義を API サーバ側のリポジトリに移すことにしま した。

Slide 17

Slide 17 text

リポジトリ分割のステップ ① OpenAPI 定義の移行 OpenAPI 定義の過去のコミット履歴は失いたくなかったので、Git のサブツリーマージをしてリポ ジトリ間でファイルを移動させました。 サブツリーマージとは、ふたつのプロジェクトがあるときに一方のプロジェクトをもうひとつのプ ロジェクトのサブディレクトリに位置づけるというもの。 ref. Git リポジトリの特定のファイルを別の Git リポジトリに移す

Slide 18

Slide 18 text

リポジトリ分割のステップ ② OpenAPI の npm パッケージ化 1. OpenAPI 定義を API サーバ側のリポジトリに移行(ステップ ①) 2. npm パッケージ化して GitHub Packages に登録するワークフローを作成 3. web と mobile からは npm パッケージを参照するように変更

Slide 19

Slide 19 text

リポジトリ分割のステップ ③ ソースコードの分離 harami_client のリポジトリから比較的影響の少ない web のソースコードを分離し、合わせてパス の引き上げを行いました。

Slide 20

Slide 20 text

リポジトリ分割のステップ ④ 元リポジトリのリネーム リポジトリ分割と並行して Expo SDK のアップグレードを行っていたため、その作業が終わったあ と元リポジトリのリネームを行いました。

Slide 21

Slide 21 text

リポジトリ分割のステップ リポジトリ分割を進める際に工夫したこと 作業自体は事前に準備した手順を淡々と進めるだけでした。幸い大きなトラブルもなく無事に終え ることができました 🙌 ● リポジトリをきれいな状態にするのは後回しにして、リポジトリ分割だけに集中する ○ クリーンアップはリポジトリという境界線ができてからの方が進めやすい ● レビューが終わってリリースできるものは早めにマージしてもらう ● 分割後に想定される FAQ をドキュメントにまとめる

Slide 22

Slide 22 text

リポジトリ分割のその後

Slide 23

Slide 23 text

リポジトリ分割のその後 ソースコードの独立性 密な依存関係については、物理的にリポジトリを分けたことで完全に依存関係がなくなりました。 当初の目的であった Expo SDK のアップグレードについても工数を抑えることができ、フレーム ワークのライフサイクルにも追従しやすくなりました。 また、独立性を得たことで Dependabot によるパッケージ更新が行えるようになりました。 ref. 理想のフロントエンドテストをたずねて三千里 - カミナシ エンジニアブログ

Slide 24

Slide 24 text

リポジトリ分割のその後 ソフトウェアのアジリティ 暗黙的な依存関係がなくなったことで、安心して CI やリリースを独立させることができました。 その結果、CI をシンプルにして高速化したり、単体で hotfix を行えるようになりました。 また、エンジニアリング組織が急拡大しているので Pull request をマージしようとすると rebase 合戦が起きていましたが、この問題も緩和することができました。

Slide 25

Slide 25 text

まとめ

Slide 26

Slide 26 text

まとめ 3 行まとめ ● 当時のカミナシにおいては Monorepo のメリットを活かせず、密な依存関係が開発生産性を 落としていました ● Monorepo はソフトウェア開発における銀の弾丸ではなく、サービスや組織の成長に伴って 最適解は変わってきます ● リポジトリ分割は神経を使いますが、やってやれなくはないです(おそらく逆も然り)

Slide 27

Slide 27 text

株式会社カミナシ https://kaminashi.jp