このドキュメントは、Reactを使ったフロントエンドのアーキテクチャを事例としてまとめたものになります。
本ドキュメントが今後開発を行うシステム、プロジェクトの参考情報となることを目的としています。
なお、このドキュメントに記載しているシステムは開発中のものであり、実際に運用されているシステムではありません。テストなどで品質を高めていく活動についても、これからになります。その点については、ご留意のうえ、ドキュメントを参照ください。
このドキュメントの事例となったプロジェクトでは、SlackのようなチャットツールをSPAで開発を行うことになりました。このドキュメントを作成している時点では、チャットアプリケーションに必要な最低限の機能のみ実装しています。
このシステムを開発するチームでは、採用する技術要素については前提や制限がないため、一からアーキテクチャを検討することになりました。開発体制としては、フロントエンドが1~2名、バックエンドも1~2名と少人数です。
これらを前提として、フロントエンドのアーキテクチャを検討することになりました。
フロントエンドの開発では、ReactやVueといったフレームワークを活用した開発が一般的です。しかし、これらのフレームワークはSPAのビュー部分しかカバーしていないため、これらのフレームワークがカバーしていない箇所の設計が必要になります。以下に示すように、フレームワークがカバーしていない箇所はたくさんあります。
ここからは、そうしたフレームワークがカバーしていない箇所を含めて、フロントエンドのアーキテクチャの検討項目ごとに説明していきます。
ここで取り上げる検討項目は、Reactを使ったフロントエンドの開発でよく取り上げられているものを参考に、PJで検討した項目になります。
開発を開始した2018年時点では、ReactとVueがSPAの開発でよく利用されていました。開発メンバの1名がReactとVueでの開発経験があるため、どちらのフレームワークでもスムーズに開発を開始できる状態でした。また、ReactとVueのライセンスはともにMITなので問題なく使用できます。
このプロジェクトではチャットアプリケーションでリアルタイムにメッセージをやりとりするため、双方向通信を実現する必要がありました。情報収集するとVueよりもReactの方が双方向通信を行う周辺ライブラリが充実していると感じ、Reactを採用しました。
なお、双方向通信以外の周辺ライブラリ(コンポーネントライブラリやコンポーネントのスタイル定義など)については選定に時間を割かずに、人気があるものや定番のものを実際に試してみて大きな問題が無ければそのまま進めることにしました。
Reactはビュー部分の機能しか提供していないので、処理フローの制御や状態管理は、別途考える必要があります。開発対象のチャットアプリケーションは画面が7個必要で、さらに今後追加が見込まれており、管理する状態数が多いという特長があります。状態数が多いフロントエンドの処理フローの制御や状態管理を自分達で実装するのは、開発とメンテナンスコストが高くなると考えました。そのため、Reactを使ったフロントエンドの処理フローの制御や状態管理を行うライブラリとして、よく使用されているReduxを採用しました。開発メンバの1名がReduxを使った開発経験があったことも採用理由の1つです。
ただし、Reduxはビューの状態までしか管理しません。非同期でのバックエンドのAPI呼び出しやバックエンドのAPIが扱うリクエスト・レスポンスとビューで扱うモデルとの変換は、自分達で設計する必要があります。
このプロジェクトでは、以下の責務配置にしました。
Reduxの標準的な構成(action、reducerなど)に以下を追加しています。
バックエンドのAPIの変更が直接ビュー側(Reduxの構成要素)に影響しないように、serviceとview-modelを設けています。さらに、バックエンドのAPI呼び出しをapiに集約することで、serviceで似たようなバックエンドのAPI呼び出し処理が発生しないようにしています。
責務配置にあるsagaは、APIの非同期呼び出しを行うライブラリです。バックエンドのAPIを呼び出す画面イベントの場合は、saga->service経由でバックエンドのAPIを呼び出した後、reducerで処理します。バックエンドのAPIを呼び出さなくてもよい画面イベント(クライアントサイドのデータのみで処理できるもの)の場合は、reducerのみで処理します。
saga->service部分のソースコード例を示します。
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import ChatRoomService from '...'
import { createChatRoomSuccess, createChatRoomFailure } from '...'
function* handleCreateChatRoom() {
whie(true) {
// takeでアクションの待ち受け
const action = yield take(CREATE_CHAT_ROOM_START_EVENT)
// serviceの非同期呼び出し
const {chatRoom, error} = yield call(ChatRoomService.createChatRoomAsync,
action.payload.userId)
if (!error) {
yield put(createChatRoomSuccess(chatRoom))
} else {
yield put(createChatRoomFailure(error))
}
}
}
ディレクトリ構成
このプロジェクトでは、責務配置に合わせて、以下のディレクトリ構成で開発しています。 component配下の分類についてはコンポーネント設計で後ほど説明します。
命名ルール
このプロジェクトでは、以下のような命名ルールを設け、複数人で開発してもメンテナンスしやすい状態にしています。
実装のポイント
<UserManagePage {...this.props}>
↓
<UserManagePageTemplate {...this.props}>
↓
<UserTable {...this.props}> (this.propsにはonEditButtonClickなどが入っている)
↓
<MyButton onClick={this.props.onEditButtonClick}>
コンポーネント設計は、コンポーネントの作成単位を決めることでコンポーネントの再利用性を高め、メンテナンス性を高めるために行います。アーキテクチャの責務配置で示したcomponentがここでのコンポーネントに該当します。
コンポーネント設計の方法としては、Atomic Designが有名です。Atomic Designとは、コンポーネントを化学に例えて、以下の5階層に分類して実装するやり方です。
コンポーネントが多い場合は、何らかの方法で整理する必要が出てくるので、Atomic Designの適用を検討した方が良いでしょう。Atomic Designの基準に沿って分類でき、開発者同士の会話がしやすくなります。
このプロジェクトでは、Atomic Designを参考に、以下の分類にしました。moleculesとorganismsの分類が曖昧になりがちなことから、以下の4分類にしています。半年ほど開発していますが、特に迷うことなく、basic(=atoms+molecules)とpart(=organisms)という分類を利用できています。
Reactを使う場合、コンポーネントライブラリの選択肢はたくさんあり、開発対象のアプリケーションの要件に応じて、選択していくことになります。コンポーネントライブラリの選択肢については、以下が参考になります。
Best React Component Libraries/Frameworks for 2018
このプロジェクトでは、ツリーや選択リストがあるElement UIとAnt Designを使用しています。
よく使われているMaterial-UIは、基本的なコンポーネントのみしか提供していないので、プロジェクトの要件を満たすにはコンポーネントの開発量が増えるため、採用しませんでした。
なお、テーブルを表示できるReact Data Gridなど、単体のコンポーネントライブラリも汎用性が高く便利なので開発時は検討した方が良いでしょう。React Data Gridは以下から様々な例を見ることができます。
他にもReactを使う場合、「react <コンポーネントの特徴>」で検索すると色々なコンポーネントが見つかりますので、要件に近いものを探して使用すると良いでしょう。
Reactを使うことで、HTMLとJavaScriptがコンポーネント化されますが、CSSはアプリケーション全体に対して影響(つまりグローバルスコープ)します。CSSについてもHTMLやJavaScriptと同じように、CSSの影響範囲をコンポーネントに閉じるアプローチがあります。Reactを使った場合は CSS Modulesやstyled-componentsを使うことで実現できます。
現在このプロジェクトでは開発メンバが使い慣れているCSS Modulesを使用しています。しかし今後はstyled-componentsの使用を検討しようと考えています。styled-componentsを使うと、CSSの定義をReactのコンポーネントと同じファイル内に記載できます。外部のcssファイルに記載が必要なCSS Modulesに比べ、コンポーネントが扱いやすくなるためです。
コンポーネントのカタログは、APIリファレンスのようなもので、開発者がコンポーネントの表示例や使用方法、具体的なソースを参照するために使用します。
コンポーネントのカタログを作成するツールとしては、Storybookが有名です。
開発メンバが多く、コンポーネント数も多い場合はStorybookを導入した方が良いでしょう。導入準備とメンテナンスのコストと、利用場面のバランスを考慮して、導入の判断を行います。
このプロジェクトではStorybookを導入していません。開発メンバが少ないこと、Atomic Designのatomsにあたるような部品は採用したUIコンポーネントのライブラリを使用しており、使用方法等はライブラリのドキュメントで確認できるためです。
デザイナーはUI/UXの設計に長けていますが、必ずしも実装に長けているわけではありません。UI/UXの設計と実装には乖離があり、実装困難なケースが出てくる可能性があります。この乖離を少なくするため、このプロジェクトではデザインガイドを作成してデザイナーと開発者の間で方針を共有しました。
実際のデザインガイドでは、以下のようなことを定義しました。
SPAでは、クライアントサイドに状態を持ち、画面切り替えを行い、ユーザーに機能を提供します。そのため、クライアントサイドでURLに応じた画面切り替えを行う仕組みが必要になります。この仕組みをルーティングと呼びます。
このプロジェクトでは、Reactでルーティングをする場合によく利用されているReact Routerを使用しています。
React/Reduxを使った場合、APIの非同期呼び出しとしては、2つのライブラリが有名です。
このプロジェクトでは、書きやすさや機能の充実さなどの理由からredux-sagaを採用しました。
基本的なAPIの非同期呼び出しであれば、take(イベントの待ち受け) -> call(非同期処理の呼び出し) -> put(次のイベントの発火)という流れで処理を記述できます。
WebSocketを使った双方向通信は少し難易度が高くなりますが、以下の公式ページを参考にすれば実装できます。今回のプロジェクトでも実際に実装を行い使用しています。
型チェックは学習コストもありますが、開発メンバ数が多い場合は導入する価値があると考えています。このプロジェクトでは、開発メンバが1~2名だったので、型チェックを導入していません。
今回は導入していないので良し悪しを言及できませんが、型チェックとしてはよく使用されているTypeScript、flowの他に、以下に記載があるようにVS Codeの型判定を使用する方法もあります。そういったツールの機能を使用するのも選択肢になると考えています。
静的チェックは、定番のESLintを使っています。
開発開始からチェックして、エラーが出た場合に設定を見直し、開発チームで認識を合わせながら進めるのが良いでしょう。
このプロジェクトでは、自動テストを実施しておらず、手動でテストしています。
今後、Reactで使用が推奨されているEnzyme、モック化やアサーションなど、一通りの機能が揃っているJestを使用予定です。
自動テストについては、ロジックが記載されるサービス層、Reducerから作成する予定です。
実際に上記のアーキテクチャで開発をしてみて感じたことは以下になります。
今後の改善としてはテストの自動化があります。このプロジェクトでは、UIデザインの変更が頻発したため、手動によるUIテストのコストがかかっています。テストの自動化をどこまでやるかを決めて対応する必要があると考えています。
このアーキテクチャ事例が今後SPAの開発を行うシステム、プロジェクトにとって、少しでも参考になればと思います。
本コンテンツはクリエイティブコモンズ(Creative Commons) 4.0 の「表示—継承」に準拠しています。
Copyright 2020 TIS Inc.keyboard_arrow_up