大規模Angular SPA開発に立ち向かうためにアプリとUIを切り分けた話

ビズリーチで「HRMOS(ハーモス)採用管理」のフロントエンドエンジニアをしています、浅井です。

前編では「AngularJSのリプレースにAngularを選んだ話」についてお話しました。 今回は、長期的にサービスを発展させていくことを念頭に置き、アプリケーションの規模もチームメンバーの人数が増えていってもスムーズに開発・メンテナンスしていくために、AngularでWebアプリケーションを再構築していく中で盛り込んだことをご紹介したいと思います。

AngularJS時代の課題

せっかくAngularに移行するからには、「AngularJS時代からの負債は引き継ぎたくない、メンテナンスしやすい仕組みにしておきたい」というのがチームメンバー全員の想いとしてありました。

まずは、AngularJS時代に実装・運用フェーズで感じていた課題を洗い出したところ、

  • ページ単位で 似たようなUIが別々に 作られている
  • ゆえに、UIの改善を行なう画面を洗い出すのが大変。すべての画面で 一貫性が担保できているのか わからない

という大きな課題がありました。

これは、(Angularの醍醐味でもある)コンポーネント指向で1から書き直せば解決できると考えました。

アプリケーションから独立した UI Components ライブラリを開発

「HRMOS採用管理」は、採用担当者向け / エージェント企業向けと、利用者に応じた複数のAngularアプリケーションが存在します。

これら複数のAngularアプリケーションで共通のUIを利用できる基盤があれば、上述した「AngularJS時代の課題」を解決できるのではないかと考え、UIに関わるコンポーネント( UI Components と呼ばれる)をAngularの1ライブラリとして別で集約することにしました。

UI Components ライブラリを GitHubの別リポジトリで管理 して、 npmのプライベートパッケージとして公開 することで、Angularアプリケーションが利用できる基盤となります。別リポジトリにすることで、採用管理アプリケーション開発とは独立したUIコンポーネントのリリースフローを回せるようになります。

プロジェクトの構成。右側のAngularアプリケーションはさらに複数個存在する

Angularアプリケーション自体の再構築を進めながら、この UI Components ライブラリも並行で開発することにしました。アプリケーションのアーキテクト設計に強いフロントエンドエンジニアと、UI設計に強いフロントエンドエンジニアという志向性の異なるメンバーがいたため、分業体制をとることができました。

まずAngularJS環境で使われているUIを洗い出し、ボタンや入力フィールドといった、インタラクションが単純でかつアプリケーション内で頻出するコンポーネントから開発に着手しました。

必要なUIの一覧を洗い出す
使われているUIを洗い出して書き出したところ。およそ30種類のコンポーネントを用意する必要がある

Atomic Design + SPA = Atomic Components

ここで、コンポーネントの粒度について少し紹介したいと思います。

この2〜3年で Atomic Design が認知され導入事例も出てきておりますが、「ワイヤーフレームを持つTemplates」「実際のテキストや画像が入るPages」という概念がSPA(シングルページアプリケーション)では馴染みにくいと考え、Atomic Design から派生しSPA向けに最適化された Atomic Components を参考にして設計を進めました。

私たちは以下のような構成でコンポーネントを分類しています。

Atoms

これ以上分解不可能なUIの最小単位。また、これ以上分解することはない粒度のUIもAtomsに配置しています。
例: Avatar, Button, Checkbox, Input Field, Tooltip, etc…

Molecules

Atomsに属するコンポーネントを繰り返し配置したもの、もしくは複数のAtomsコンポーネントを配置してグループ化したもの。内部で状態を持つことはありません。
例: Dropdown, Nav, Pagination, etc…

Organisms

Moleculesに属するコンポーネントを複数並べたもの、もしくはOrganismsの中で別のOrganismsを呼び出す必要があるもの。内部で状態を持ち、状態はOrganismsの中だけで完結するものであり、他のOrganismsや上位のEcosystemに干渉を受けることはありません。
例: Popover, Tabs, RichtextEditor, ImageCrop, etc..

Ecosystem

Atomic Design ではTemplatesに相当するレイヤー。Reactでいう Container Component 的な立ち位置のコンポーネントで、状態管理が求められるレイヤー。 Organisms, Molecules, Atoms Component を用いて Ecosystem Component を実装していきます。

Environments

ページ全体を指すもので、Atomic Design ではPagesに相当するレイヤー。SPAであればEnvironmentsは基本的には1つだけとなります。また、ページ全体で持たせたい状態(例えば、サイドバーの開閉状態)は、Environmentsレイヤーで持っています。

HRMOS採用管理では原則として、Organismsまでの粒度のコンポーネントは UI Component ライブラリ側で持たせ、Ecosystemsに関してはいわゆる Container Component として各Angularアプリケーション側で実装するアプローチを採っています。

複雑なComponentの実装には、Angularの公式ライブラリを活用する

開発当初は多数のサードパーティAngularライブラリを入れてトライ&エラーを繰り返しましたが、現在ではほぼ Angular team が開発している Angular Material & Angular Component Development Kit (CDK) をベースに、HRMOSオリジナルのスタイルを反映しています。

Angular CDK は、カスタムUIコンポーネントを作るための根幹となるものです。一言で言えば、UIライブラリを作るためのライブラリと言えます。

例えばOverlayというコンポーネントは、画面上に別のコンポーネントを重ねて表示するために必要な機能がCDK側で実装・提供されているため、ModalやPopoverといった類のコンポーネントでは、「表示/非表示の制御」と「HRMOSのデザインレギュレーションに沿ったスタイリング」を実装するだけで済むようなるため、開発工数の短縮ができます。

年に2回あるAngularのメジャーバージョンアップになるだけ早く追従していきたいこと、各コンポーネント単位でユニットテストが記述されており高い品質が担保されていることも、Angular Material/CDK を採用した理由となりました。

リリース作業にかかる負担を減らしながら、頻繁にリリースできる仕組み

開発中の UI Components ライブラリを実際のAngularアプリケーションに導入すると、コンポーネントに持たせるパラメーター( @Input アノテーション。Reactでいうpropsに相当するもの)が足りず、想定していたインタラクションが実装できなかったり、実装が不完全で不具合になることがありました。

ある意味UIコンポーネントを分離したがゆえの弊害ではありますが、この問題を逆手に捉え、頻繁にリリースできる仕組みを作ることにしました(リリースするとはいっても git tag でバージョン番号のタグをセットし、 npm publish するだけです)。

UI Components ライブラリを npm publish した後は、Angularアプリケーション側のpackage.jsonに新しいバージョン番号をセットして npm install することで、最新のコンポーネントが利用できる流れになっています。

頻繁にリリースが行われると、Angularアプリケーション側の開発を行うエンジニアはCHANGELOG(変更履歴)を見たくなるのは当然のことでしょう。とはいえCHANGELOG.mdファイルを毎回記述するのも面倒なものです。この負荷をなるべく軽減したい想いがあったため、standard-version を導入しました。

standard-versionは、gitの commit log を元に、CHANGELOG.md(変更履歴)の自動生成と、次のバージョン番号を自動的に付けてくれるツールです。 git commit 時に Commit Message Guideline に準拠したコミットメッセージを記述する必要がありますが、リリースの際にドキュメントを作る手間が省けるメリットの方が大きいことから、導入を決めました。

UI Components ライブラリを開発したことによるメリット

ライブラリを作る側

  • フロントエンド実装ができるデザイナーは、エンジニアを介さずともUIの微修正をアプリケーションに反映できる
    • 見た目に関わる実装、改善をデザイナー自らメンテナンスできるように。

ライブラリを使う側

  • レイアウト構成と使うコンポーネントが決まっていれば、デザイン知見があまりないエンジニアでもスタイルガイドに準拠した画面を実装することができる
    • CSSの実装が苦手なエンジニアでも作業を完結できるように。

UIに関わるコードが UI Components ライブラリとして1ヶ所に集約され、利用アプリケーションを実装する体制が整ったことで、前述の課題は「似たようなUIが別々に作られることはなくなり、結果として画面の一貫性が保たれる」ことで解決できたかなと思っています。

なにより、Angularアプリケーションのエンジニアが、 アプリケーションロジックの実装に専念できる環境を整えられたこと がメリットとして大きかったと感じています。

最後に

Angularの知識が少ない中で UI Components ライブラリの開発に着手するのはチャレンジングな取り組みであり、リソースの面でも身の丈にあっていないアプローチを採ってしまったのではないかと不安を感じた時もありましたが、およそ1年をかけてHRMOS採用管理で使われている主要なUIを揃えることができました。

ライブラリとして独立させたことで、部署内の有志が取り組んでいるサイドプロジェクトの管理画面に UI Components ライブラリを用いることで開発工数が短縮できた、といった効果もありました。

エンジニアが UI Components ライブラリを活用して管理画面をサクっと作ることもできる。
エンジニアが UI Components ライブラリを活用して管理画面をサクっと作ることも。色々揃っていて便利とのお声もいただきました

コンポーネントの粒度や切り方に関しては様々なアプローチがあり、私たちもこれでベストだとは思っていません。

特にMoleculesとOrganismsの分類は悩ましく、私たちは内部に状態を持つかどうかを分類の基準の1つにしていますが「MoleculesとOrganismsのヒエラルキーが逆転しているのでは?」と感じるケースも実際にあったりします。

ただ、分類にあたっては厳格な運用は行なわず、迷ったときはコンポーネントを実装する際にフロントエンドエンジニアメンバーで話し合った上で決めています。

以上、Angularのコンポーネント開発の一例として、参考となれば幸いです。お読みいただきありがとうございました。