『Atomic Design周りについての私見』の補足

先日、ITのむずかしいを簡単にする会 - LT会 #5というイベントでAtomic Design周りについての私見という発表をしてきました。

しかし、15分枠と勘違いして作った(実際は10分枠)+当日思いついた話しておきたいことを考えなしに突っ込んだ結果スライドが40枚に登ってしまい、すごく早口での発表になってしまったので、補足を含めた内容を記事にします。

※ この記事はタイトルの通り個人的な主観が多分に含まれた内容になっていて、一概に「これが正しい」というものを示すものではありません。プロジェクトやチームの特性によって正しい方法は変化し得ますので、参考程度の内容とお考え下さい。

そもそもAtomic Designとは?

f:id:ponday:20181203225751j:plain

概要はいろいろなWebサイトで解説されているのでそちらに譲りますが、いわゆるUIデザインの手法として提唱されているものの一つです。

コンポーネントを原子(Atoms)、分子(Molecules)、有機体(Organisms)といった化学の用語で表現します。

1つのページが複数の部品を持つのではなく、複数の部品の組み合わせでページが構成されている、という考え方です。

よく次のような図が使われるので、1度は見たことがあるという方も多いのではないでしょうか。

f:id:ponday:20181203225933j:plain

理屈はかんたん。しかし...

ページをコンポーネントの組み合わせで表現する。理屈はこれ以上無いほどにかんたんなのですが、実際Atomic Designを適用して開発をしてみると次のような悩みが尽きず、やりづらさが拭えません。

f:id:ponday:20181203230408j:plain

なぜ?

ではなぜAtomic Designでの開発にやりづらさを感じるのかというと、Atomic Designが「そもそもアプリケーション開発の設計をターゲットにしたものではない」という点に尽きると思っています。

最初に述べたとおり、Atomic DesignはあくまでUIデザインの手法であってアプリケーションの設計手法ではありません。

このあたりの認識があいまいになってしまっていて、いざAtomic Designを適用してアプリケーション開発を始めてみたら「あれ、思ってたよりつらい......」という状態に陥っている人が多いのではないかなと思います。

Atomic Designとアプリケーション設計

Atomic Designがもたらしたもの

f:id:ponday:20181203231126j:plain

ではなぜ最近Atomic Designの名前をよく聞くようになってきたのかという点にですが、個人的に命名の勝利という側面もあるかなと思っています。

コンポーネントの階層と役割をきちんと体系付けて名前を与えたこと。それを化学になぞらえた名前にしてイメージを湧きやすくしたことで、エンジニアとデザイナーの共通言語としての役割を担えるようにしたというところに価値があるのかなというのが個人的な印象です。

さらに言うと、近年のフロントエンドにおいてJSフレームワーク(ReactやAngular、Vue.js)を用いて開発する中で確立されてきたコンポーネント志向とマッチしていたこともエンジニア層に受け入れられた要因ではないかと。

これまで一般的だったページ → 部品というデザインの流れに逆らって、部品 → ページという考え方で作られたUIデザイン手法だった、というところも大きかったのではないでしょうか。

Atomic Designが考慮しないもの

f:id:ponday:20181203232612j:plain

繰り返しになりますがAtomic DesignはあくまでUIデザインの手法なので、アプリケーションの設計に適用しようとすると色々と足りない部分があります。

基本的にCSSやJSの領域について、Atomic Designは面倒を見てくれません。これらの領域については、Atomic Designとは別に+αの設計が必要になってきます。

特にロジックの分離が問題で、「デザイン上のコンポーネントの境界」と「プログラム的なコンポーネントの境界」が必ずしも一致しない、というより一致しないことの方が多いです。

「ロジックは全部同じだけどデザインが異なるコンポーネントは、プログラム的に1つのソースにまとめるべきか、複数に分割して作るべきか」

といった部分をあらかじめ取り決めたうえで設計をする必要があります。

+αの設計を考える

f:id:ponday:20181203234442j:plain

Atomic Design + αを考えるとき、先のような内容について検討する必要があります。

基本戦略

+αの内容を検討する前に、基本戦略としてAtomic Designで定義されたレイヤー(Atoms, Molecules, etc)に、プログラム的な視点での役割を決めておきましょう。

個人的には、以下のように定義する場合が多いです。

f:id:ponday:20181204205117j:plain

Atoms、Moleculesはあくまで汎用的な部品として、ページを形作るのはOrganisms、Templatesというイメージです。

f:id:ponday:20181204205557j:plain

そのような分け方をするとき、Atoms、Moleculesはできるだけ汎用性を保って部品化できるようにすべきだと思っています。なるべくアプリケーション固有のデータに依存する作りにはせず、そのアプリケーション内だけではなく、他のプロジェクトでも流用できるようなものになるのが理想的です。

例えばUIライブラリとして提供するのはこのあたりまでかなと思います。

逆にOrganisms以上の層は他のアプリケーションで再利用することのない固有のコンポーネントというイメージです。

f:id:ponday:20181205005418j:plain

ここまでAtoms、Molecules、Organisms、Templatesについてのみ定義をしてきましたがこれには理由があります。

元々の定義によると、PagesはTemplatesに具体的なデータが流し込まれたものです。つまりPagesは最終的にレンダリングされてエンドユーザーが見ている画面と同じになるはずで、Pagesを個別にコンポーネントとして作成する必要はないだろう、というのが個人的な意見です。

Pagesをコンポーネントとして定義することがあるとするなら、UIテスト用にTemplatesにデータを流し込むコンポーネントとして定義する、という感じかと。

コンポーネントの管理

これは開発、運用面の話で、同じ機能を持つコンポーネントを作ってしまわないためにどうするか、という点です。

ここは設計とかあまり難しい話ではなくて、Storybookなどのコンポーネントカタログをきちんと用意して、使いたいコンポーネントが用意されているかどうかを誰でもすぐ確認できるようにしておけば良いと思います。

f:id:ponday:20181205010133j:plain

コンポーネントの依存関係

f:id:ponday:20181205010449j:plain

元々のAtomic Designの定義では、あるコンポーネントはその一つ下のレイヤーのコンポーネントのみに依存することになっています。

TemplatesはOrganismsにのみ、OrganismsはMoleculesにのみ、MoleculesはAtomsにのみ、という極めてシンプルな依存関係です。

しかしながら、現実的にこの制約はかなり辛いと言わざるを得ません。例えばOrganismsでAtomsも使いたいというニーズですが、実際に作ってみるとこういう場面は少なくなく、厳密にやろうとするあまりAtomsにパラメータを横流しするだけのMoleculesが生まれてしまう、という状況に陥ります。

そこでこのあたりの制約をもう少し柔軟にして、次のように定義しました。

f:id:ponday:20181205011229j:plain

比較的規模が小さく、理想的にはUIライブラリとして切り出せるようにしたいAtoms、Moleculesはオリジナルの定義に則り、アプリケーション固有の層となるOrganismsについてはオリジナルの定義から外れて依存するレイヤーを広げた形です。

TemplatesはあくまでUI上のレイアウトやそれに紐づく状態を管理するレイヤーと位置づけているので、最終的に出来上がった部品 = Organismsを配置するのを役割としてOrganismsのみに依存する、としました。

コンポーネント間のデータの受け渡し

f:id:ponday:20181205011908j:plain

UIデザインをコードに落とし込んで行くとき、開発者側が1から検討する必要があるのがコンポーネント間のデータの受け渡しをどうするか、という点です。

とはいえ、ここは愚直に「親から子にはプロパティを渡して、子から親にはイベントを経由してデータを渡す」という原則に立ち返って設計するのが一番良いのではないかと思います。

f:id:ponday:20181205012954j:plain

親から子にプロパティ渡しというと、「またバケツリレーになるのか......」と思われるかもしれませんが、単なるバケツリレーではなく「親は子が必要とするプロパティを適切に振り分けていく」イメージです。

例えばTemplatesでは複数のオブジェクトを持っているけど、Organismsに渡すときにそのうちの1つだけを渡す。そのOrganismsが受け取ったのはオブジェクトだけど、Moleculesにはそのうち必要な値だけを選択して渡す......という感じ。

このように原則は親→子のプロパティ渡し、子→親のイベント伝搬で、例外としてFluxのようなObserverパターンをベースにした状態管理が入っている場合が挙げられるのかなと思います。

状態管理

f:id:ponday:20181205013616j:plain

近年のフロントエンド開発、ことフレームワークを利用したSPA開発においてFluxベースの状態管理は必ずと言って良いほど入ってくるので、状態管理が絡んだデータフローについても検討が必要になってきます。

先に示した図は個人的にイメージしているデータフローです。Storeと書いているのが状態管理層で、ReactならRedux、AngularならNgRxや状態管理用のService、Vue.jsならVuexをイメージして貰えれば良いです。

subscribeはStoreからデータを読み出す操作、dispathはStore内のデータを更新する操作と読んで貰えればOKです。

f:id:ponday:20181205013817j:plain

先程、Atoms、Moleculesは可能な限り汎用的に保ちたいという話をしました。

状態管理層はアプリケーション固有のデータ、ロジックの塊なので、汎用性を保つという観点から行くと、Molecules、Atomsからは一切アクセスさせないようにすべきです。

状態管理に何のライブラリを使っているのか、そもそも状態管理をしているのかすらMolecules、Atomsが知る必要はなくて、あくまでOrganismsから与えられるプロパティを使ってビューを構築するようにしておきます。

f:id:ponday:20181205014919j:plain

Storeはアプリケーション固有仕組みになるので、そこにアクセスするのも同様にアプリケーション固有のレイヤー(= Templates、Organisms)になります。

では、Templates、OrganismsからStoreに対するアクセスポリシーも検討しておきます。考えうるパターンはいくつかありますが、個人的にはOrganismsが直接Storeからデータを読み出す操作(subscribe)は禁止することが多いです。次点でOrganismsからStoreへのアクセスを禁止するパターン。

ここはどのやり方も一長一短あるので、チーム内できちんとルールが決まっていれば良いと思います。

まとめ

f:id:ponday:20181205020214j:plain

何度も繰り返しているように、まずきちんと認識しておくべきことは「Atomic DesignはあくまでUIデザインの手法である」、ということです。

なので実際にアプリケーションを開発する、という視点で見ると制約が強すぎたり、そもそもプログラム的な問題は考慮されていなかったりとつらい部分が多くあります。

当然、開発に適用するためには+αのルールづくりが必須で、事前に決めておくべき(決めておかなければならない)ルールも少なくありません。

個人的にはAtomic Design原理主義的に厳密に実践しようとするのは悪手だと思っていて、上手くバランスをとって取り入れたルールを作って使うのが賢い使い方かなと思います。

現状Atomic Designで開発してみた、という記事を読むと、ここまで書いた内容と似たルールをチーム毎に決めて運用しているという状況になっているので、そのうちアプリケーション開発を念頭においたAlt Atomic Designのようなものが登場すると予想しています。Atomic Designっぽい作り方はしつつもそれが登場するまで待って、将来的にはそちらに乗ったほうが幸せかもしれません。

f:id:ponday:20181205021108j:plain

とはいえ、コンポーネント志向ライクなUIデザイン手法として、概念的にはシンプルで良いものだと思います。これからもこの周辺の情報は抑えていきたいと思います。