🐹

ニコニコ生放送のBCD Design導入事例

2023/12/15に公開

ニコニコ生放送でフロントエンドを担当している 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 による分類を行っています。

ディレクトリ構成 ライブラリ層.png

BCD Design の記事でも紹介している BCD Design の内部で Atomic Design を使用する例に該当します。

わりと粒度の整理が付きやすいものばかりなので、この運用をしていますが、Atomic Design ではなく MUI のような分類を組み合わせても問題ありません。

プロジェクト層

プロジェクト層は関心の単位の説明における文脈を持つ世界が中心であり、 domain が最も大きく成長します。

casecommon は意外と少なく、理由は後述しますがプロジェクト層にも base は存在します。

全体像

common domain を展開すると見にくくなってしまうので、第二階層までの全体像から。

ディレクトリ構成 概要.png

common の内部

ディレクトリ構成 common.png

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-anchorpremium-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 系に関して

よく HeaderFooter というコンポーネントを作ると、ソートしたときにバラバラの場所に分散してしっくりこないということがあるかと思います。

これも BCD Design の明名の観点から考えると、 HeaderFooter という名前では Base に所属するはずですが、それらの内部構成は一般的に決まっているものではなく、何かに依存して決定されていることに気付きます。

それがサービスやサイトあたりになるわけですが、サービスよりもいわゆるWEBサイト特有の構成物やレイアウトに強く影響を受けるので、特定のサービス独自のサイト依存と捉えるとうまくまとまります。

特定のサービス独自のサイトなので domainsite がまさにそのための場所になります。

button 系が少ない理由

ニコニコ生放送のフロントでは、アイコンは smart-svg を使用し、CSSで表示する実装にシフトしているため、一部のボタンを除いて単体のコンポーネントとして用意する機会が減っています。

現在 case にコンポーネントとして残っているボタンも整理される予定です。

tag が tag-card になっている理由

Tag というコンポーネント名を使わなくなった理由 をご参照ください。

複数形を使わず xxx-list にしている理由

リスト系のコンポーネント名 Images と ImageList はどっちが有利か をご参照ください。

ちなみに、Base に tabs のようにタブを選択してパネルを切り替えるUIを置く場合は、一般的なUIとして確立されたものであり、単独のUIの名称なので問題ないと考えています。

まとめ

今回はニコニコ生放送において実践している BCD Design の実例や、適切な分類に導くための様々な視点やテクニックを紹介しました。

コンポーネント数が多く、ドメインも複雑化するニコニコ生放送のプロジェクトの中でも、体系的に秩序を保ち続けられる BCD Design は非常に強力なツールです。このような状況を作ってしまえばスケールに対する不安は全くありません。

皆さんもニコニコ生放送の分類を参考に BCD Design にチャレンジしてみてはいかがでしょうか?

既存のディレクトリ構成を変えてみたらどうなるかというシミュレーションを行ってみるだけでも色々なものが見えてくると思います。

参考

株式会社ドワンゴでは、様々なサービス、コンテンツを一緒につくるメンバーを募集しています。 ドワンゴに興味がある。または応募しようか迷っている方がいれば、気軽に応募してみてください。

Discussion