こんにちは、キカガクでエンジニアをしている北田です。 今回は私達がリリースしている学習プラットフォーム「kikagaku.ai」 のフロントエンドのディレクトリ構成を紹介していきます。
対象読者
ディレクトリ構成
早速ですが、私達は下記の構成でフロントエンドの開発をしています。 あまり構成が定まっていないものや今後削除予定のものは一部割愛していますが、基本的には下記の5つのディレクトリでフロントエンドの実装を行っています。 今回はこの中でも /components と /domain に絞って紹介しています。
-| /components -| /domain -| /libs -| /pages -| /styles -| /hooks
/components
/components はアプリケーションのビューを構成しているディレクトリになります。/components の中でも、さらに下記のように分けています。
-| /common -| /layouts -| /pages -| /context
/common
こちらは アプリケーション全体で共通で使っているパーツを定義するコンポーネントで、例えば、Button コンポーネントや Toast コンポーネントといったものになります。中身の文字や文字カラー、Width といった css 要素を props として渡してアプリケーション全体で仕様が異ならないようにしています。
チームのメンバーやデザイナーさんへ確認するときも、ここに配置している Storybook を共有することで齟齬がなくなるようにしています。
Storybook については、また別の記事にまとめて行きたいと思います。
/layouts
こちらでは Footer や Header といったアプリケーション共通のレイアウトを定義しています。
kikagaku.ai ではログイン前ページ、ログイン後のダッシュボード、学習ページの3つでそれぞれレイアウトが異なるので、それらをまとめている場所という位置づけになります。
/pages
こちらは /src/pages にわたすコンポーネントで、1つのページを表現するためにコンポーネントを配置しています。
例えば この下に /mypage というディレクトリを切っているものがありますが、そこではユーザーのマイページ画面をまとめている階層といった感じになっています。
また少し話がそれますが、ディレクトリやファイルの命名規則は Vercel のCommerce を参考にしていますので、ぜひこちらもご覧ください。
Vercel/commerce
github.com
/context
こちらでは useContext で定義しているグローバルな値を {children} としてラップされているコンポーネントであればどこからでも呼び出せるようにするためのコンポーネントです。 useContext とは コンポーネント間の状態の受け渡しを可能してくれる Hooks で、キカガクでは主にユーザーのログイン情報 (Firebase Auth からの返り値)を保存しています。 /context ディレクトには AuthContext.tsx という1ファイルのみが定義されています。useContext について詳しくは公式ドキュメントをご覧ください。 React 公式 reactjs.org
... return ( <AuthContext.Provider value={{ currentUser: currentUser, isLoading: isLoading, }} > {children} </AuthContext.Provider> );
/domain
このディレクトリでは Firestore のモデルを定義し、API 通信を行い取得したデータを加工してフロントエンドに返す一連の流れを一方通行的に定義している階層になります。 イメージをつかみやすくするために、キカガクのサービスドメインに沿って User や Course モデルを使って /entiries から /usecases までの簡単な流れを画像にまとめました。
/entities:
チームによってはモデルと言うところもあるかもしれません。ここでは Firestore のスキーマ(コレクション構造)に沿った型を定義しています。
上の画像だと黄色い背景部分になりますが、例えばユーザーを保存しているコレクションは Users
として型を定義しています。
/repositories:
ここではデータベースとの API 通信を行っている層になります。ここの階層で気をつけているのが、ここでは一切固有のロジックを記述しないことです。/repositories は Firestore 側で用意されている set()
, get()
, update()
, delete()
の4つのメソッドの処理を行うのみのため、固有のロジックを加えてしまうと、/usecases の関数と責務が混在してしまいます。
Firestore の構造に沿ったデータとそのままやり取りを行う部分であり、ここの層に対してテストを記述することもありません。
/usecases:
最後にユースケース層です。/entiries でモデルの定義 → /repositories で DB 通信 ときているので、ここではフロントへ渡すためのデータの加工を行います。上の画像で説明すると、取得してきた複数のコースに対して、フロント側で「日本語のコース名を map で表示させたい」といった場合にその要件に沿って値を加工します。下記のようなことを行っているのがこの層になります。
const coursesAttribute = await getCourses(); //repositories からコース一覧を取得 //coursesAttribute の型は [{name: '', url: '', id: }, {}, {}, ...] のようなものとします。 const jaCourseNames = coursesAttribute.map((course, _) => course.name); return jaCourseNames
冒頭でも述べたとおり、 kikagaku フロントエンドの比重が大きく、DB 通信やデータの加工も同じレポジトリで行います。 そのためデータを取得するためのリクエストから、実際にユーザー画面に表示するまでの流れを一方通行にしておくことでロジック層の煩雑化を防いでいます。
まとめ
今回は kikagaku のフロントエンドのディレクトリ構造について紹介しました。 キカガクのエンジニアチームでは今日取り上げたようなディレクト設計の知見がある方やサービスの設計経験が豊富なエンジニアをまだまだ募集しています。 ぜひ興味があればご応募ください!カジュアル面談も行っております!