ニコニコ生放送のBCD Design導入事例
ニコニコ生放送でフロントエンドを担当している misuken です。
今回はニコニコ生放送において実践している BCD Design の実例を紹介しようと思います。
大規模なシステムの大量のコンポーネントを BCD Design で分類した場合、どのように整理されるのかをご覧いただき、皆さんのディレクトリ構成の参考にしていたけると幸いです。
BCD Design とは
BCD Design とは2020年に自身が発表した、とても合理的なコンポーネント分類法です。(コンポーネントに限らず、ディレクトリ構成全般に応用可能です)
"命名" ではなく、"明名" というアプローチで単語の意味や性質を利用して分類することにより、誰の主観も入らない体系的なディレクトリ構成を実現できます。
Atomic Designでモヤモヤしていた方や、ディレクトリ構成に悩みを抱えていた方から、以下の評価を頂いています。
- モヤモヤがスッキリした
- この考え方しっくりくる
- 迷わなくなった
個人的に特に良いと感じている点は以下。
- 明名された名前だとスムーズに納得を得られる
- 一覧を見たとき違和感のある名前に気付きやすい
- その場合、他の人も違和感を感じているので修正しやすい
- チーム内で的はずれな名前がそもそも出てこなくなる
- 命名や分類に掛かるコストや悩みを丸ごとカットできる
- 規模が大きくなるほど精度を上げられる
- 名前が整理されることで分類以外の設計やコーディングにも好影響がある
ニコニコ生放送のフロント構成のおさらい
まずはニコニコ生放送のフロント構成のおさらいから始めましょう。
フロントの構成は昨年のアドベントカレンダーで 2022年ニコニコ生放送WebフロントのView事情 を書いたときから変わっていません。
VC (View Component)
いわゆる表示の責務を持つプレゼンテーションコンポーネント。
APIや外部リソースと結合していないため、Storybookで静的に単体表示確認が可能。CC (Container Component)
APIとの結合やドメインの状態管理を行い、VC用のPropsを生成するコンポーネント。
FES (Front-End Server)
バックエンドからページのための情報をかき集めるサーバー。
CCが規定したPropsを渡して、Server Side Renderingする。構成図
- 1つ1つはパッケージ
- パッケージを囲う四角形がモノレポ
- モノレポの抽象度によって層(依存関係)がある
層 リポジトリ パッケージ 説明 ライブラリ層 NVC NVC 独自開発のUIライブラリ。
OSSレベルで汎用的なパッケージ。プロジェクト層 NicoliveComponent styles ニコニコ生放送に依存したSassの定数やmixinのユーティリティ。 stories ニコニコ生放送に依存したStorybookのユーティリティ。 VC ニコニコ生放送のスタイルを適用されたコンポーネント群。 CC MobXで実装された汎用的なドメインロジック。 アプリケーション層 pc-program-watch VC/CC/FES 番組視聴ページ。 pc-program-list VC/CC/FES 一覧系ページ。
共通部分が多いので内部でさらにcommonとtop recent history ranking等複数パッケージに別れています。pc-program-create VC/CC/FES 番組作成ページ。 spweb VC/FES スマホ用ページ。
歴史的経緯でPC系と構成が違うが、VCの使い方は同じ。
BCD Design によるディレクトリ構成
前提
アプリケーション層も含めると量が多すぎるので、今回はライブラリ層とプロジェクト層のディレクトリ構成を紹介します。
- pc-program-watch(視聴ページ)にあるプレーヤー関連のコンポーネントはほとんど含まれません
- snackbarやdialog系のディレクトリが多いものの、コンポーネントは一元化されています
- common以降のそれらは関心ごとに文言を管理するためファイルとなっています
- すべての文言は Storybook の一つのストーリーで表形式で参照できるようになっています
- イメージを伝えることを目的としているため、コンポーネントの具体的な内容にはあまり踏み込みません
- 公開用に少し調整している部分もあります
ライブラリ層
ライブラリ層は関心の単位の説明における、文脈を持たない世界であり、UI的関心にあたります。
自作でUIライブラリを作らないのであれば、ここはMUIなどのライブラリに相当します。
BCD Design では Base にあたりますが、他の分類がない状態で base
ディレクトリを作っても意味がないので base
を省略した上で Atomic Design による分類を行っています。
BCD Design の記事でも紹介している BCD Design の内部で Atomic Design を使用する例に該当します。
わりと粒度の整理が付きやすいものばかりなので、この運用をしていますが、Atomic Design ではなく MUI のような分類を組み合わせても問題ありません。
プロジェクト層
プロジェクト層は関心の単位の説明における文脈を持つ世界が中心であり、 domain
が最も大きく成長します。
case
と common
は意外と少なく、理由は後述しますがプロジェクト層にも base
は存在します。
全体像
common
domain
を展開すると見にくくなってしまうので、第二階層までの全体像から。
common の内部
domain の内部
※ 画像が大きすぎたため三分割しています
BCD Design のルールに従ったコンポーネント名が並ぶことで、量が多くても秩序ある世界を実現できていることが伝わるかと思います。
プロジェクト層の base の役割
上の表では省略してありますが、プロジェクト層にも base
ディレクトリは存在します。
ライブラリ層のNVCは特定のアプリケーションに依存しないため、機能的な部分を除いてスタイルを含みません。
プロジェクト層の base
ディレクトリにアプリケーションで使用する各コンポーネントのスタイルや、プロジェクト内でよく利用するパターンを用意し、表示パターンをカタログ化して Storybook 及び VRT(Visual Regression Testing) で網羅的に確認できるようにしています。
- ライブラリ層にある NVC
- コンポーネントの機能のみに責務を持つ
- プロジェクト層の
base
- プロジェクト内で使用するスタイルの責務を持つ
- プロジェクト内でよく利用するパターンの責務を持つ
Case と Common の使い分け
BCD Design を発表してから地道に研究してきた結果、 Case と Common は処理の対象(Common)と処理自体(Case)に分けるとうまく管理できることがわかってきました。
ここはかなり細かい部分になるので、別記事を書くかもしれませんが、この記事でも allegation
follow
search
を例にある程度説明します。
allegation はデータ
common
にある allegation
(申し立て)は動詞である allege
の名詞系です。
これは主に通報のことを指すのですが、 "申し立て" や "通報" と聞くと動詞にも使えるため、 Case と Common どちらに分類するか迷いが生じます。
しかし、実際にどのようなことが起きるのかを考えてみると答えが見えてきます。
実際に起きていることは以下。
申し立てたい内容をフォームに書いて送信する
つまり、ここで言う "申し立て" とは処理そのものではなく "申し立て内容" つまりデータを送信(処理)するということを意味しています。
// 元の言葉 = 本質的な意味
申し立てる = 申し立てを送る
これは "お問い合わせ" で考えるとよりわかりやすくなります。(動詞感の強い "問い合わせ" でも成り立ちます)
- お問い合わせリンク or お問い合わせボタンはお問い合わせフォームを開くものであり、お問い合わせ処理そのものではない
- お問い合わせフォームの submit ボタンは "お問い合わせボタン" ではなく "送信ボタン" である
"お問い合わせ" というデータに対して送信という処理を行っているわけです。
リアルな世界で "通報する" 場合でも通報内容を相手に伝えます。"通報する" という行為は、通報にあたる情報を送る処理のことを指すため、この "通報" はデータであり処理の対象(Common)となるわけです。
その一方 "追加" や "送信" のように処理自体を表す単語は処理の対象にしようとしても "追加を送信する" のようになって意味が成立しません。
つまり、これら処理自体を Case にまとめれば境界がはっきりし、きれいに分類されます。
ちなみに "設定" の場合も見事にこの法則に合致します。
- 設定ボタンは設定パネルや設定画面を開くものであり、設定を処理するものではない
- 設定を終えるときに押すボタンは保存ボタンや適用ボタンである
処理の対象である "設定" を保存(処理)することで成り立っていることがわかります。
setting
は名詞であり動詞にはなりません。処理の対象は名詞であり "申し立て" "通報" "問い合わせ" も名詞として扱うことで "設定" と同じパターンになることが理解できます。
follow は関係
フォローは "フォローする" のように動詞としての印象が浮かびますが、 "フォローを追加する" "フォローを解除する" のように "フォロー"(関係) をどのように処理するか、という視点で捉えます。
実際、フォローするとフォロー関係は状態として残ります。
これは関係性の追加と削除を意味していて、"関係" をとある文脈に向けて具体化したものが "フォロー" であり、それは処理の対象としての関心事になるのです。
// 元の言葉 = 本質的な意味
フォローする = フォロー関係を追加する
フォローボタン = フォロー関係追加ボタン
アンフォローボタン = フォロー関係解除ボタン
DB的な概念になりますが、"リレーション" を表すものは "関係" として扱えるということを意味します。
search は処理
"検索" は処理自体を表す単語で "検索" 自体を処理の対象にしようとしても実行以外のやりようがありません。そのため search
は Case になります。
ただし "検索条件" であれば保存もできるので処理の対象であり search-condition
は Common になります。
単純に先頭の search
だけ見て判断できないことにはなりますが、 "検索履歴検索ボタン" があったら "検索履歴" が処理の対象で、 "検索ボタン" が処理自体を実行するボタンであることは事実なので、それを捻じ曲げるとどこかに支障が出てきます。
例えば、"設定" は名詞なので設定パネルは common/setting/setting-panel
になりますが、検索設定パネルを作ろうとしたら case/search-setting-panel
から Common の設定パネルを参照することになってしまうといった具合です。
case
search-setting-panel // ❌ common/setting 内への参照が発生
common
setting
setting-panel
以下なら case
から common
を参照することはありません。
case
search-button
common
search-setting
search-setting-panel // ✅ setting も case/search-button も参照できる
setting
setting-panel
これらが意味すること
開発者が複雑化を回避するために潜在的に実現したいと思っていることは、単語の出現順の正規化による単一方向性の確保です。
それが確保できれば循環参照や逆方向への参照といった問題も発生しなくなります。
そこで、本質的な意味合いでは単語の出現順が "処理の対象 を 処理する" になる法則性に従い、処理の対象(Common)と処理自体(Case)に分ければ、自然と理想的な分類に収まるということなのです。
もちろん、単純に先頭の単語だけで分類するだけでもほとんどのプロジェクトではそれほど支障なく運用できるはずなので、どれくらいの精度を目指すかはプロジェクトメンバーの意向によって決める必要があると感じています。
Case と Common 内の秩序が乱れてきたなと感じたらこのことを思い出すと良いでしょう。
修飾子の扱い
premium-member-registration-anchor
は premium-member
というドメインではなく member
側に含まれます。
これは premium
という修飾子が付いても会員が主体であり、関心が分散しないよう member
にまとまっていたほうが望ましいからです。(他に premium-*
が増えた際、それらでまとまっていても意味がなく関心の千切りになるため)
もし premium-member
というディレクトリが欲しい場合は 共通部分をスマートに管理するディレクトリ構成 を参考に以下のような構成にできます。
// もしも会員の種類が3つあるとした場合
member
_main // 会員の関心
premium-member // プレミアム会員の関心
special-member // スペシャル会員の関心
名前の長さに関して
planning-event-participation-broadcaster-list-section
(企画イベント参加放送者リストセクション)など、コンポーネント名としては長いと感じるかもしれませんが、開発チーム内で名前の長さが問題に上がることはほとんどありません。
その理由はあくまで BCD Design の明名に従っており、一つの単語でも抜いてしまうとコンポーネントの依存内容が欠けてしまうため、過不足のない名前のほうがメリットがあると理解しているためです。
もし、名前が適切に明名されていないとすれば、そこを指摘して名前に反映されますが、名前の長さがどうなるかは結果でしかありません。
もちろん、責務を色々詰め込みすぎて複雑化している場合はそれ以前の問題なので、関心の対象が一つに定まらないような名前になる場合は適切に分割する必要があります。
※ 上で述べたコンポーネントは、JavaScriptで event
が別の意味を持つ関係で企画的なイベントを示す planning-event
という名前を適用しています
base/switch に関して
switch
は動詞だから Case に入るのでは?と思うかもしれませんが、Switch のように一般的にUI名として確立されているもの自体であれば Base に入ります。
閉じるボタン(close-button
)のように、切り替えボタン(switch-button
)という切り替え処理を実行する単なるボタンであれば Case に入ります。
domain/site 系に関して
よく Header
や Footer
というコンポーネントを作ると、ソートしたときにバラバラの場所に分散してしっくりこないということがあるかと思います。
これも BCD Design の明名の観点から考えると、 Header
や Footer
という名前では Base に所属するはずですが、それらの内部構成は一般的に決まっているものではなく、何かに依存して決定されていることに気付きます。
それがサービスやサイトあたりになるわけですが、サービスよりもいわゆるWEBサイト特有の構成物やレイアウトに強く影響を受けるので、特定のサービス独自のサイト依存と捉えるとうまくまとまります。
特定のサービス独自のサイトなので domain
の site
がまさにそのための場所になります。
button 系が少ない理由
ニコニコ生放送のフロントでは、アイコンは smart-svg を使用し、CSSで表示する実装にシフトしているため、一部のボタンを除いて単体のコンポーネントとして用意する機会が減っています。
現在 case
にコンポーネントとして残っているボタンも整理される予定です。
tag が tag-card になっている理由
Tag というコンポーネント名を使わなくなった理由 をご参照ください。
複数形を使わず xxx-list にしている理由
リスト系のコンポーネント名 Images と ImageList はどっちが有利か をご参照ください。
ちなみに、Base に tabs
のようにタブを選択してパネルを切り替えるUIを置く場合は、一般的なUIとして確立されたものであり、単独のUIの名称なので問題ないと考えています。
まとめ
今回はニコニコ生放送において実践している BCD Design の実例や、適切な分類に導くための様々な視点やテクニックを紹介しました。
コンポーネント数が多く、ドメインも複雑化するニコニコ生放送のプロジェクトの中でも、体系的に秩序を保ち続けられる BCD Design は非常に強力なツールです。このような状況を作ってしまえばスケールに対する不安は全くありません。
皆さんもニコニコ生放送の分類を参考に BCD Design にチャレンジしてみてはいかがでしょうか?
既存のディレクトリ構成を変えてみたらどうなるかというシミュレーションを行ってみるだけでも色々なものが見えてくると思います。
参考
- 2022年ニコニコ生放送WebフロントのView事情
- BCD Design
- アンチパターンを理解して package by feature へ
- 共通部分をスマートに管理するディレクトリ構成
- Tag というコンポーネント名を使わなくなった理由
- リスト系のコンポーネント名 Images と ImageList はどっちが有利か
- Sass製SVG爆速表示ライブラリのご紹介
株式会社ドワンゴでは、様々なサービス、コンテンツを一緒につくるメンバーを募集しています。 ドワンゴに興味がある。または応募しようか迷っている方がいれば、気軽に応募してみてください。
Discussion