microCMS

microCMSのWebフロントエンドにクリーンアーキテクチャを採用した話【前編】

この記事は公開後、1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

microCMSの大西です。microCMSには2022年の5月に入社しました。普段は開発本部長として組織的な業務、エンジニアのサポート、開発全体の大まかなタスクの方向性を決めといった業務を行なっています。

microCMSでは昨年中盤以降にWebフロントエンドの設計パターンを刷新しました。採用した設計パターンはクリーンアーキテクチャです。
2回に分けて大西と森茂(フロントエンドテックリード)がmicroCMSのWebフロントエンドの設計パターンについて紹介します。

前提としてmicroCMSのフロントエンドはReact、状態管理にはuseState/useContextを使用しています。APIのキャッシュにReact Query(TanStack Query)を使用しています。比較的素朴な設計になっています。

背景と課題

microCMSはサービス開始から数年が経過しており、バックエンドもフロントエンドも初期コードベースを見直す時期に差し掛かっていました。しかし、なかなか技術改善の機会を作れていなかったため、自分が入社後にバックエンド・フロントエンドの技術改善を議論する会を開催しました。

最初に刷新が決まったのはバックエンドです。バックエンドメンバーとの話し合いで、AWS AmplifyとNode.jsで記述されているものをTerraformとGolangに移行することが決まりました。Golangの設計パターンもクリーンアーキテクチャを意識したものになっています。これは別の機会に紹介しようと思います。

その後フロントエンドメンバーとフロントエンドの技術課題について話し合いをしました。フロントエンドの改善というとReact ComponentとFigmaの一致やStorybookなどデザイン領域の改善もありますが、今回は「技術」改善ということでデザインに関連する部分以外での改善点を話し合いました。そこで上がってきた課題は以下のようなものでした。

  • TypeScriptへの移行がされていないファイルがある
  • フォルダ構成が整理されていない、共通認識が持てていない(この時はcomponents, hooks, utils, types, store, ducks...などが存在していた)
  • コンポーネントにロジックが書かれている
  • コンポーネントが肥大化しすぎている
  • 状態管理がSingle Source of Truthになっていないケースがある
  • useState, useEffectを気軽に使いすぎて処理が複雑になっている
  • 命名規則がバラバラ
  • HelpButtonやCustomStatusなどシンプルかつ小さなatom的なコンポーネントでも状態やロジックを持っている


上記に挙げた課題は一部ですが大小さまざまな改善点または理想のようなものがありました。これらはmicroCMS特有のものではなく皆様も感じられる一般的なものかと思われます。

解決の方向性

技術改善に限りませんが、一般的にブレスト的に上がってきた課題に対して1:1対応で直接的に解決策を考えることはあまり効果的ではありません。例えば、「Componentのファイルを細かく分ける」「useState、useEffectはなるべく使わない」「ComponentのロジックをCustom Hooks切り出す」のように個別具体的な対策では対症療法になりがちです。
特に今回のように大小さまざまで観点も異なる問題が多く出てきた場合は1つ1つに向き合う時間もありません。なるべく効果的な改善策を検討する必要があります。

話し合いを進める中でフォルダ構成、Componentファイルの肥大化、Componentにロジックが書かれている、状態管理あたりが直近の課題であることがなんとなくわかってきました。つまりこれは責務の分離(関心の分離)ができていないということです。

これに対してmicroCMSでは「クリーンアーキテクチャ」の思想を採用しました。

なぜクリーンアーキテクチャを選んだのか

前述の課題に対し、microCMSでは「クリーンアーキテクチャ」の思想を採用しました。クリーンアーキテクチャはポピュラーな設計パターンの思想で、「依存の方向性」「責務の分離(関心の分離)」「依存性逆転の法則」を重視しようというものです。この基準に沿った構成にすることで責務が分離され変更に強くなりテスタブルになります。

フレームワークやライブラリにロックインせず素朴に構成可能であり、クロスプラットフォームな設計パターンの思想で、テスタブルであること、またReactの世界にも影響(制約)を与えないので採用することにしました。例えばReactコンポーネントの領域はatomicデザインの設計パターンを採用することも可能です。
ここ最近はフロントエンドのユニットテストにも力を入れており、テスタブルであることは極めて重要です。

クリーンアーキテクチャとは

設計パターンの厳密な定義について語ろうとすると意図しない議論になりがちですので、ここでは厳密に正しい説明はせず多くの解説記事にお任せしたいと思います。
クリーンアーキテクチャは本来はディレクトリ構造や書き方のルールの集合体やフレームワークではなく、「依存の方向性」「責務の分離」「依存性逆転の法則」などをしっかり守って、レイヤー構造にし、疎結合で外部に影響を受けずテスタブルで変化に強い設計にしようという概念的なものです。ただしこれでは抽象的すぎるため、「View・Presenter・UseCase・Repository・Entity」のような具体的なレイヤー名に落とし込んだパターンで有名なものがいくつか存在しています。後述しますが、microCMSではこの5つのレイヤー構造のパターンのことをクリーンアーキテクチャと呼んでいます。
オニオンアーキテクチャなどもありますが、本質的な部分は変わらないと思います。

筆者の大西はネイティブアプリ(iOS)開発が得意で責務の分離やテスタブルな設計パターンの経験があります。ネイティブアプリ開発ではクリーンアーキテクチャは人気で多くのプロジェクトで採用されています。
自分が調べた限りですと、Webフロントエンドにクリーンアーキテクチャを導入することは珍しいことかもしれません。しかしクリーンアーキテクチャは他のプラットフォームでも人気な設計パターンで、フレームワークなどにロックインされず素朴かつ強力です。

ReactのWebフロントエンドの定番設計パターンは確立されていないように感じており、microCMSオレオレ設計パターンを検討するよりは健全だと考えました。
またDDD的な意味合いで機能ごとのディレクトリ構造を作る方法もありますが、筆者の経験上あまりうまく行ったことがなく結局機能横断のロジックをどこに配置するかなどで迷いがちです。

例えば責務の分離の観点で改めて考えると刷新前のmicroCMSのhooksというディレクトリは文法名であり、実際にはバリデーションだったりAPI通信だったり状態管理だったりがあるわけです。そうなると当然責務の分離は意識されなくなってしまいます。よくあるのはAPI呼び出しの処理と同じファイルにトーストを表示するような処理を書いてしまうことです。API呼び出しとUIの処理が一緒になっているのは好ましくありません。

microCMSでのクリーンアーキテクチャの構造

microCMSでのクリーンアーキテクチャはView, Presenter, UseCase, Repository, Entityの5つのレイヤーを定義しました。各レイヤーの役割は以下のとおりです。

View

  • 渡されたデータをレンダリングするだけのレイヤー
  • Reactコンポーネント
  • Presentational/Containerコンポーネントで構成される


Presenter

  • Reactコンポーネント(View)と接続し、Viewを操るレイヤー
  • 状態管理を行う
  • ViewからのUIイベントを一時受けする
  • Reactとの接続があるためCustom Hooksになることがほとんど
  • 他のHooksに依存するものはここで呼び出す


UseCase

  • microCMSならではのビジネスロジックを書くレイヤー
  • 使い回しのできるユーティリティ関数
  • 外部(DB、API、ブラウザ、React)に依存する処理を書かない
  • ページによっては使わない場合もある


Repository

  • 外部に依存する処理を書くレイヤー
  • DB、API、ブラウザと接続しデータを取得する、保存する


Entity

  • アプリケーション全体で使うようなオブジェクトを定義するレイヤー
  • User,Media,Contentなどのinterface(type)やclassを書く


これは概念的なものであり、実際のディレクトリ構造がこの5つに分かれるというわけではありません。実装レベルでどうなるかというのは続編の森茂さんのブログで紹介します。

microCMSでのレイヤー構造を定義したところで、クリーンアーキテクチャでは「依存の方向性」「責務の分離」「依存性逆転の法則」が重要と捉えられており、これを守ることで責務の分離が実現できます。3つについてmicroCMSではどのように取り組んでいるか説明します。

責務の分離

これはレイヤーを定義し、各レイヤーの責務を明確にすることで、どこに何を記述するかを明確にするということです。microCMSではView、Presenter、UseCase、Repository、Entityの5つとしました。各レイヤーの役割は前述のとおりです。ViewにはReact Componentを配置、PresenterはViewのReact Componentと接続し、Viewを操るロジックを記述、UsecaseはViewに依存しないビジネスロジックのような処理を記述、RepositoryはAPIやブラウザなどの外部の世界とのやり取りを記述し、それを隠蔽します。Entityはサービス全体で使い回すようなデータを定義します。このようにレイヤーの責務を明確にすることで、どこに何を記述するかが明確になりコードの見通しが良くなり、変更に強い設計になります。新しく入った人が見ても理解しやすいというメリットもあるでしょう。

依存の方向性

これはViewはPresenterに依存する、PresenterはUsecaseに依存するといった依存できるレイヤーを決めるということです。コード的にいうとimportできるレイヤーが決まっているということです。このように依存の方向性を明確にすることで変更に強い設計になり、コード参照に秩序が生まれます。また依存先がルール化されるのでモック化しやすかったりテスタブルな設計になります。
microCMSでは前述の図のように依存関係を定義しました。このルールを超えてのimportは禁止です。Entityは型定義に近い存在ですのでグローバルにimport可能です。

依存性逆転の法則

これは少し理解が難しいルールです。依存先のレイヤーの実装を直接呼び出さず、自分のレイヤーで依存先のinterfaceを定義し、DI(Dependency Injection)を使用して、そのinterfaceにのみ依存するように設計すると、依存先のレイヤーの変更を受けにくくさらに変更に強い設計にできます。詳細な説明は「依存性逆転の法則」などで検索してみてください。

依存性逆転の法則について簡単な解説をしましたが、結論を言うとmicroCMSではこのルールは採用していません。本来であればUseCaseはRepositoryの実装を直接呼び出すような作りは好ましくありません。UseCaseはビジネスロジックであり、外部のAPIやDB、ブラウザの変更の影響を受けないように設計する必要があります。つまりUseCaseはUseCaseの中でRepositoryのinterfaceを定義し、DIによってそれに依存するようにすべきです。

しかし現実的にはレイヤーが分かれていれさえすれば問題なく、強い静的型付け言語ではinterfaceを定義しないとテスタブルにできない場合があるため必須ですが、動的型付けのJavaScriptやTypeScriptでは依存先をjestなどで簡単にモック化することができます。

JavaScriptやTypeScriptでclassやinterface、DIを使うことはライトな記述ができる強みを消してしまうため、設計の正しさよりも現実的な記述の楽さ、コードの見通しの良さを優先しました。

この設計によって得られたもの

今回の設計パターンを採用したことで、以下のようなメリットがありました。

  • どこにどのような処理を書くか迷うことが減る
  • 処理を呼び出す際もどこにどのような処理があるか迷うことが減る
  • レイヤーで分けているため、責務が明確になり、強制的に関心が分離される
  • 変更に強い設計になる
    • 例えば、APIの仕様が変わった場合、Repositoryのみを変更すれば良い
  • レイヤーで分けているため、テスタブルである
  •   例えば、Presenterのテストは依存先のUsecaseのみをモック化すれば良い
  • etc...


一般的なデメリットとしては依存性逆転の法則のためにinterfaceに依存しようするとコードの記述量が増え複雑度が増すことが挙げられますが、microCMSはこのルールをあえて採用しないことでシンプルさを保てています。

おわりに

microCMSのWebフロントエンドにクリーンアーキテクチャを採用しました。すでに数ヶ月運用されており、既存のコードも徐々に移行されています。また明らかにユニットテストの数が増えてきました。
この設計パターンは慣れていないとハードルが高いと感じると思うので、最初は社内勉強会を開催するなどして、Reactの世界を壊すようなものではない、素朴な設計パターンであることを伝え最初のハードルを下げるようにしました。

今回の取り組みにおける最大の収穫は設計パターンによってわかりやすくなったことではなく、開発メンバー全員が責務の分離や依存、テスタブルを意識するようになったこと、Webフロントエンドに閉じない設計パターンを学べたことではないかと実は密かに思っています。

最後に注意点ですが、本ブログでは設計パターンの厳密で正しい解説をしているわけではありません。特にクリーンアーキテクチャという単語はいろいろな捉え方をされているため、正しい知識を得たい方はぜひWebで調べてみてください。

今回はここまでです。次回は実際のディレクトリ構造やコードベースでの解説を弊社のフロントエンドテックリードの森茂さんに解説いただこうと思います。
最後までお読みいただきありがとうございました。

まずは、無料で試してみましょう。

APIベースの日本製ヘッドレスCMS「microCMS」を使えば、 ものの数分でAPIの作成ができます。

microCMSを無料で始める

microCMSについてお問い合わせ

初期費用無料・14日間の無料トライアル付き。ご不明な点はお気軽にお問い合わせください。

お問い合わせ

microCMS公式アカウント

microCMSは各公式アカウントで最新情報をお届けしています。
フォローよろしくお願いします。

  • X
  • Discord
  • github

ABOUT ME

大西智也
microCMSでエンジニアリングマネージャーをしています。iOS・機械学習・ドライブ・旅行・ゲーム・散歩も好きです。