「フロントエンド技術の波を乗り越える!Vue2からReactへの移行とアーキテクチャ設計による堅牢化」というタイトルで登壇しました #cm_odyssey
はじめに
リテールアプリ共創部の中野です。
クラスメソッド設立20周年イベントとして開催されたClassmethod Odysseyのオンラインイベントにて2024/7/30に登壇しました。
「フロントエンド技術の波を乗り越える!Vue2からReactへの移行とアーキテクチャ設計による堅牢化」というタイトルでお話したセッション資料を公開します。
セッション概要
フロントエンド技術の変化に翻弄されることなく、ソフトウェアのコアを守り抜くことが重要です。
今回は、Vue2からVue3への移行検討で課題に直面した事例を紹介します。大規模な書き換えが必要だったため、Reactへの移行とフロントエンドコードのクリーンアーキテクチャによる再設計を行い、コアロジックを守りぬくことができました。フロントエンド技術の変化に左右されないソフトウェア設計の方法を解説します。
登壇資料
登壇内容
登壇資料の内容のポイント部分を、一部掲載します。
このブログ内の内容以外にも詳細を確認されたい場合は、登壇スライドをご参照ください。
目次
- Vue2からReactへリライトを意思決定した理由
- リライトヘ当たって直面した課題
- 課題を解決するための取り組み
- フロントエンドクリーンアーキテクチャ導入解説
- リライト後の変化
- 学んだ教訓
Vue2からReactへ移行を意思決定した理由
理由1: Vue2のEOL
Vue2は2023年12月末に EOL (End of Life) を迎えていました。
正規ルートだと「Vue2からVue3」、現トレンドだと「Vue2からReact」にするか、世間では大きく2つのルートに分かれていました。
Vue 2 の最新リリースである 2.7.16 は、Vue 2 の最終リリースであり、2.7 機能の最終的な修正が含まれており、さらに、Vue 3 との型の整合性がありました。
理由2: Vue2からVue3の移行のコスト
Options APIを利用している場合、Composition APIへの置き換えが必要でした。
Vue2だと、各コンポーネントのthisに状態を変更するロジックが依存するため、関心事の単位で関数をまとめることが難しく、Options APIを利用するコード周辺に分散しがちで「状態を持つ関数」の再利用がしにくいという問題がありました。
Vue2に関連付属するライブラリの置き換えが必要でした。
理由3: React主流による保守体制確保の容易性
Reactは現行のトレンドであり、Vueよりも主流になっています。
今後の追加機能開発を進める上で、Reactの方が開発工数が少なくて済むと判断しました。
理由4: チームの技術スタックをそろえることによる効用
私たちのチームは、技術スタックを可能な限り統一する方針をとっており、技術への認知負荷を低減し、顧客への価値提供速度を上げることができました。
理由5: メンバー変更によるナレッジ不足の補強
ソフトウェアプロダクトにおいて担当者の交代は「あるある」です。
担当者が変わっても継続してユーザーへ価値提供を促進できるよう、情報を理解しやすい形にとどめておくことが必要でした。
理由6: 今後のビジネス価値への寄与に期待できた
リライトすることによるステークホルダーの見返りを考えました。
例: ビッグリライトする代わりに新機能を追加で入れる、セキュリティ面が補強される最新のアーキテクチャにできる、キャッシュ機構を導入しレスポンスが高速化し品質向上
アーキテクチャ概要
クリーンアーキテクチャとは?
クリーンアーキテクチャは、システムの関心事を分離するためにソフトウェアをレイヤーに分割する設計手法です
Robert C. Martin氏が提唱しました(公開ブログ:2012年8月、書籍:2017年)
クリーンアーキテクチャで設計されたシステムの特徴
- フレームワークに依存しない
- 特定のフレームワークやライブラリに依存しません
- フレームワークやライブラリをツールとして使用でき、システムをその制約にべったり合わせることは不要です
- テスタビリティ(テスト容易性)向上
- ビジネスルールはUI、データベース、Webサーバーなどの外部要素なしでテスト可能です
- UIに依存しない
- UIは簡単に変更可能で、ビジネスルールに影響を与えません
- 特定のデータベースに依存しない
- OracleやPostgraSQL、MongoDBなどのDBが変わってもアプリケーションの機能を維持できます
- 外部API、外部システムに依存しない
- ビジネスルールは外部の世界について何も知る必要がありません
※ The Clean Code Blog - The Clean Architecture - を参考にしました
サンプルコード
- アルバムと写真を表示する簡易アプリ
- Reactベースでフロントエンドをクリーンアーキテクチャで設計
アプリの実行
- Webサーバー起動の場合
開発用のWebサーバーを起動するには、以下のコマンドを実行します。
npm run serve -w photo-album
これにより、Viteでサーバーが起動し、http://localhost:3000
へアクセスするとアプリを閲覧できます。
- CLI起動の場合
データを取得するためのCLIを実行するには、以下のコマンドを実行します。
npm run cli -w photo-album
実行後、すべてのアルバムを取得するか、アルバムIDごとに写真を取得するかなどのタスクを選択するように求められます。
例:
npm run cli -w photo-album
> cli
> npm run start ./src/framework/cli/main.ts
> start
> npx tsx ./src/framework/cli/main.ts
? CLIで実行するタスクを選択してください すべてのアルバムを取得する
? 実行しますか?controller=すべてのアルバムを取得する yes
{
"message": "全てのアルバムデータを取得します",
"logLevel": "INFO"
}
{
"message": "use-case: find-album-use-case-impl",
"logLevel": "DEBUG"
}
{
"message": "アルバムデータの取得に成功しました",
"data": [
{
"id": 1,
"title": "quidem molestiae enim"
},
--- 以下、省略 ---
npm run cli -w photo-album
> cli
> npm run start ./src/framework/cli/main.ts
> start
> npx tsx ./src/framework/cli/main.ts
? CLIで実行するタスクを選択してください アルバムIDで写真を取得する
? 実行しますか?controller=アルバムIDで写真を取得する yes
? アルバムIDを入力してください 50
{
"message": "アルバムID 50 の写真データを取得します",
"logLevel": "INFO"
}
{
"message": "use-case: find-photo-use-case-impl",
"logLevel": "DEBUG"
}
{
"message": "写真データの取得に成功しました",
"data": {
"photos": [
{
"id": 2451,
"title": "odio animi nobis cumque",
"url": "https://via.placeholder.com/600/8fef3c",
"thumbnailUrl": "https://via.placeholder.com/150/8fef3c"
},
--- 以下、省略 ---
ディレクトリ構造
以下は、クリーンアーキテクチャに基づいて設計されたプロジェクトのディレクトリ構造です。この構造により、Coreの部分がタマネギ(クリーンアーキテクチャの同心円)の内側、Frameworkの部分がタマネギの一番外側に配置させるようにして、各層の責務が明確にしました。
また、Webブラウザから表示するだけではなく、CLIからもデータを取得できるようにしています。
.
└── src
├── core // コアロジック
│ ├── domain // ドメイン層
│ │ ├── entities // エンティティ定義
│ │ ├── repository // リポジトリのインターフェース定義
│ │ └── support // API Clientなどのサポート関数の定義
│ ├── infrastructure // インフラ層のインターフェース格納先
│ │ ├── api-client // API Clientの実装詳細
│ │ ├── logger // Loggerの実装詳細
│ │ └── repository // リポジトリの実装詳細
│ ├── usecase // アプリケーションユースケース層
│ └── util // Coreで利用可能なユーティリティ関数
├── di-container // 依存注入周り
│ ├── env-util.ts
│ ├── register-container.ts
│ └── service-id.ts
└── framework // フレームワーク層
├── cli // CLI
│ ├── controllers // Usecase層への橋渡し
│ └── main.ts // CLIのエントリポイント
└── web // Web
├── presenters // Usecase層への橋渡し
├── EntryPoint.tsx // Webのエントリポイント
├── components // Reactのコンポーネント群
├── pages // React Routerのページ配置
└── util // Web用ユーティリティ関数
レイヤー間の関係図
以下の図は、各レイヤー間の関係を示しています。各レイヤーは独立しており、依存関係は内側のレイヤーに向かって一方向にのみ存在します。
レイヤー毎の定義
層 | 説明 |
---|---|
Domain層 | ドメインロジックを定義する層。ビジネスルールやエンティティを含む |
Infrastructure層 | 外部システムとのやり取りを担当。APIクライアントやデータベースアクセスを含む |
UseCase層 | ユースケース。ドメイン層とインフラ層をつなぐ |
Framework層 | ユーザーインターフェースを担当。Reactコンポーネントを含む |
まとめ
以下、登壇資料のまとめです。
- フレームワークやライブラリをつかうと楽に早く開発できる
- フレームワークやライブラリのクラスやメソッドを使い込んで思いっきり結合してくれることを提供側は望んでいるし、利用者も同様
- しかし、フレームワーク依存しすぎると自分たちが意図しない方向(フロントエンドのトレンド、EOL、破壊的変更など)で書き換えやリファクタリングが必要になってくる場合がある
チームでライブラリやフレームワーク自体を統一する標準化をとるのもいいが、ソフトウェアアーキテクチャを統一して解決する考え方もある
参考
- Vue.js 公式: Vue 3 移行ガイド
- React 公式: Reactリファレンス
- Chris Birchall 著: レガシーソフトウェア改善ガイド
- Robert C. Martin 著: Clean Architecture 達人に学ぶソフトウェアの構造と設計
- Neal Ford, Rebecca Parsons, Patrick Kua 著: 進化的アーキテクチャ 絶え間ない変化を支える