🧙‍♂️

巨大SPAフロントエンド 垂直分割への第一歩

2024/12/09に公開

こんにちは、株式会社 AI Shift の @KK_sep_TT です。
本記事は AI Shift Advent Calendar 2024 の 9 日目の記事となります。

巨大SPAの現状

AI Shift には複数のプロダクトがあり、それらのプロダクトの管理画面は1つのSPA上に構築されていました。

これらのプロダクトが同じ SPA 上に構築されていたのには以下の背景がありました。

  • それぞれのプロダクト同士にシナジーがあるので同じ管理画面で扱いたい
  • 共通のコンポーネントを使いたい

このような背景のもと巨大な SPA が爆誕していました。しかし、これには以下の問題がありました。

  • 新しい画面を作る時に旧アーキテクチャに揃える必要がある
  • ライブラリのアップデートの影響範囲が大きく追従が困難
  • 1つのプロダクトへの変更が他のプロダクトに影響を与えてしまう

AI Shiftの現状としては、変更が必要なプロダクトと変更がほとんど必要ないプロダクトが混在している状況でした。

この場合、プロダクトD に高頻度リリースが発生しますが、その影響が他のプロダクトに波及しないようにしたいです。

そこで各プロダクトを垂直分割することを計画しました。今まで1つの巨大な SPA で動いていたアプリケーションをそれぞれ別の SPA としてデプロイする計画です。

Vercel でも 似たようにプロダクトを垂直分割した事例が紹介されています。

https://vercel.com/blog/how-vercel-adopted-microfrontends

ちなみに垂直分割の対義語として水平分割があります。

  • 水平分割: 1つのパス(ページ)の中に複数のアプリケーションが存在。
  • 垂直分割: パス(ページ)ごとにアプリケーションが分割される。

分割するターゲットを絞る

今の SPA の上には4つのプロダクトが乗っており、これを一気に4分割するのは現実的ではありませんでした。そこでターゲットを1つのプロダクトに絞ってそのプロダクトを分割することを第一目標としました。

変更が多いプロダクトは分割した時に最もメリットが大きいので、分割するターゲットは最も変更が多いプロダクトにしました。

垂直分割の第一歩

垂直分割のために、まず分割するプロダクトのコードを今の SPA コードベースから切り離す必要があります。1つの SPA を2つの SPA に割るには、切り出した新しい SPA で以下の準備が必要になります。

  • ビルド
  • ルーティング
  • CI/CD
  • デプロイするインフラ

通常の開発と並走してこれらを整備し、さらにコード分割を同時に行うのは難しいと判断し、最初はドメインのコード分割にのみ集中することにしました。

この SPA のレポジトリは pnpm を使ってモノレポ管理していたので、新たにパッケージを作成してそこに分割するプロダクトのコードを移動させました。

packages/
├─ product_A_B_C/
└─ Product_D/ ◀︎ new!

このとき、package.json も別になるので、分割したプロダクトではライブラリも既存に引っ張られずに自由に技術選定&バージョン管理できるようになりました。

新しいディレクトリ構成に

新しいパッケージへのお引越しと同時にディレクトリ構成も刷新しました。以前はレイヤー単位のディレクトリ構成だったのですが、機能単位のディレクトリ構成にしました。

packages/
├─ product_A_B_C/
└─ Product_D/ ◀︎ このパッケージの中のディレクトリ構成を刷新
   ├─ pages/ 
   ├─ features/
   │  ├─ components/
   │  ├─ hooks/
   │  └─ functions/
   └─ shared/

移動の前に移動先パッケージに空のディレクトリを作成し、そこに徐々にコンポーネントを移動させていきました。このとき、機能(feature)単位で移動させることで段階的な移行を実現しました。

切り出したパッケージを既存SPAと繋ぐ

パッケージ分割後もビルド、デプロイは既存と同じです。
なので、切り出したパッケージを既存の SPA に繋ぎこむ必要があります。

切り出したパッケージには pages/ というディレクトリがあります。ここにページ単位のコンポーネントを配置しています。
既存のSPAはここからページコンポーネントを import してルーティングで使用するようにしています。

依存のルールをESLintで設定する

切り出したプロダクトのコードが既存のコードベースに依存を持ってはいけないので、eslint-plugin-importを使用して既存コードからの import を禁止するルールを設定しました。

https://github.com/import-js/eslint-plugin-import

Next Step

プロダクトをパッケージ分割してディレクトリ構成も変えたので、当初抱えていた3つの課題のうち2つは解決することができました。

  • 新しい画面を作る時に旧アーキテクチャに揃える必要がある ✅ 解決
  • ライブラリのアップデートの影響範囲が大きく追従が困難 ✅ 解決
  • 1つのプロダクトへの変更が他のプロダクトに影響を与えてしまう

あとは実際にビルド工程まで分割して別のSPAとしてデプロイすれば垂直分割の完了です。
本当はアドベントカレンダーまでに完全に垂直分割して記事にまとめたかったのですが、間に合わなかったのでパッケージ分割まで記事にまとめることにしました😥 完全に垂直分割が完了したらまた記事にまとめようと思います!

最後に

AI Shiftではエンジニアの採用に力を入れています!
少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか?
(オンライン・19時以降の面談も可能です!)

【面談フォームはこちら】
https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459

AI Shift Tech Blog

Discussion