ニコニコ生放送がwebサービスを大人数で開発する際に辿ってきたフロントエンド アーキテクチャ

この記事は[ドワンゴ Advent Calendar 2018](https://qiita.com/advent-calendar/2018/dwango) 25日目の記事です。

ドワンゴでニコニコ生放送のPC Webフロントエンジニアをやっています、 @kondei です。

いつまでも新人気分だったのに、いつの間にか入社5年目になり、この前ナチュラルに新卒に歴史的経緯を語る立場になっていて勝手にショックを受けました。いにしえの時代（最後期のとはいえ）から現在まで実際に開発して知っているフロントエンド開発者が数人になってしまって貴重なので、いい機会なのでまとめようと思います。
内容的には [ニコニコ生放送の watch ページを MobX で作り直している話](https://www.slideshare.net/_kondei/watch-mobx)と重なる部分もあります。

# はじめに
まず、大人数のフロントエンド開発でアーキテクチャをどうするべきかの一つの解を得たので、それを述べます。

そして、どうしてそうなったのか、つまりニコニコ生放送が過去にどういう問題を抱えてきて、どういう解決策を講じてきたのか、自分の知る範囲で **フロントエンドのアーキテクチャ面** からざっくり述べます。


# 前提
今、生放送チームのバックエンドとフロントエンドの開発者（デザイナー含む）はそれぞれ20人くらいいます。
また、8~9年とかなり長く続いているサービスを開発しています。

また、生放送webにおいてはPCのwatchページとプレーヤーが一番機能が多く常に開発が盛んであり開発者が多く、それ以外のPCページやSmart Phone(SP)のページはそれに比較すると長期スパンでは落ち着き気味で開発者が少なめであるという特徴があります（比較的です）。

少人数で開発する場合や、長期的に保守するか不明なプロダクトの場合、などはまた違うスタイルが適切だと思います。実際、後述するようにSPは若干違う技術スタックや単位でリポジトリを構成しています。でも大きくは変わりません。

# TL;DR
- 責務で分業して人数でスケールしよう
- 強い型付け言語で、ドメインモデルとチーム間のインターフェースを定義しよう
- モノリシックやめよう
- まず横（技術）に切らず、縦（提供したい機能）に切ろう
  - 例：「ページ」で切ったリポジトリや、マイクロサービス
  - その後横に切って、横もなるべく揃えて、いいとこ取りしよう
  - 技術を横に揃えると効率が上がりスケールアップするけど、トップレベルでズバッと横に切ってしまうと人数でスケールアウトしにくくなる

# 現在最新の方針
```
PC watchページのように開発が盛んなページを、以下の3層に分けた1つのリポジトリを作る。

- Front-End Server (FES)
  - バックエンドからページのための情報をかき集めるサーバー
  - CCが規定したPropsを渡して、Server Side Renderingする 
- Container Component (CC)
  - 状態管理、APIなどからデータ取得
  - VCにPropsを渡して描画
- View Component (VC)
  - 見た目の管理

また、開発が落ち着いたページは、上の単位でひとまとまりとして更にまとめたモノレポに置いて管理を楽にする
```

最新の方針がこうなった経緯と、上がそれぞれ何かを後述します。

# 昔の歴史
## 生1 (2007?~現在)
偉大なる初代。

### 技術スタック
- PHP
- Flash
- ECMAScript 3
- prototype.js
- jQuery

### アーキテクチャ
- モノリシック
- サーバーサイド
  - PHPで自社フレームワーク
- クライアントサイド
  - 割と皆モジュール単位で雰囲気でjsを設計して書いてた気がする
  - 部分的にknockout.jsがあったり、jQueryが使われてた気がする

### 良かったこと
- サービスを生んでくれた
- ビルドがないので（修正すべき箇所が分かっている状態なら）修正とデプロイは速い

### 抱えていた問題
- たくさん

## 生2 (2013~現在)
問題点がいっぱいあって、 [「エンジニア歴17年の俺が、事業系の開発タスクをバンバン投げてくる非エンジニアに、保守の必要性を死ぬほど分かりやすく説明する。」]( https://note.mu/gonjyu/n/nd7bf3efa0728) みたいなことになっていたので、手始めにPC webのwatchページの部分から作り直されました。
[Developers Summit 2014 「Play2/Scalaでドメイン駆動設計を利用した大規模Webアプリケーションのスクラム開発の勘所」](https://www.slideshare.net/sifue/developers-summit-2014-play2scalaweb) に詳しいです。 ちなみにurlが `live2.~` になってるやつです。

### 技術スタック

- Scala
- Play2 framework
- Groovy Template
- Flash
- TypeScript
- jQuery
- gulp

### アーキテクチャ
- モノリシック
- サーバーサイド
  - DDD
- クライアントサイド
  - 割と皆モジュール単位で雰囲気でjsを設計して書いてた気がする
  - 部分的にRxJsが使われたりしたが、ずっと模索していて結局解が見つからなかったのが実情

### 良かったこと
- DDDという概念が開発に根付いた
- TypeScriptによりフロントエンド開発に型の概念が持ち込まれ、大規模開発しやすくなった

### 抱えていた問題
- フロントエンドがくっついていて、バックエンドとリリースフローが一緒だった
  - デプロイ時間がScalaビルドで50分以上…
  - バックエンドの切り戻しにより、フロントエンドの機能リリースも延期
- フロントエンドとしては洗練されていなかった。
  - 途中までnpm package による開発じゃなかった
  - フロントエンドのフレームワークも導入されていなかった
- モノリシックなせいで、やり方が揃っていない部分が気になる
  - 何度も「どういうアーキテクチャで書くべきか」「ここ古いやり方だけどどうする？」みたいな議論をした
- サーバーサイドのHTMLテンプレートも、Groovy Template がつらいし、Scalaによるコードとかなり直結しており、スキルセット的につらい

## マイクロサービス化（2016~現在）
生2になってもつらいので、サーバーサイドがマイクロサービス路線になっていきます。また、徐々に **バックエンドサーバーとフロントエンドサーバーが完全に分離** されはじめます。これはいくつかの革命的な効果がありました。

また、これも重要なことなのですが、フロントエンドのコードはバックエンドとは別のリポジトリで管理するようになっていきます。
この後は自分はフロントエンドをメインに開発したのでそこを述べていきます。

# 最近のフロントエンド部分で抱えていた問題点と解決策
## 第一世代HTML5プレーヤー（外部プレーヤー）
iframeとかで外部のサイトに張れるプレーヤーです。
PCにもSPにも貼れます。

### 技術スタック
- TypeScript
- react
- scss

### アーキクチャ
reactコンポーネントが樹状になるようにして、なるべくルートに状態を集めた。原始的ですね。

### 抱えていた問題
- React Componentが状態を持ちまくって巨大な責務になっていく
- scssがグローバルスコープなのでクラス名を決めるのがつらい
  - CSS in JS するか、 CSS Modules の導入するか悩んだ

## 第二世代HTML5プレーヤー（PC watchページのプレーヤー）
今動いているやつです。
この辺から ViewComponent と ContainerComponent のような分離が始まりました。
ViewComponentについては @misuken さんの [React + TypeScript + CSS Modules によるコンポーネント指向フロントエンド開発の流れと知見](https://qiita.com/misuken/items/5ab7c7f47b245706a67c) や、 @ln-north さんの [コンポーネント指向フロントエンド開発におけるデザイナーの参画について](https://qiita.com/ln-north/items/53c23beb3f82abf64a4c) という素晴らしい記事を見てください

### 技術スタック
- TypeScript
- react
- scss
- css modules

### アーキテクチャ
- [facebook/flux](https://github.com/facebook/flux)
- 全StoreをEventEmitterにして、ルートで全部監視（！）して、どこかに変更があるたびに全Props再生成

### 解決した問題
- 見た目の全てを担当するVCとの分離により、それを使う側がfluxでプレーヤーの状態だけを管理すれば良くなった
  - デザイナーさんがscssのみに集中でき、開発しやすくなった
  - → 今後VC方式は生放送webに全面的に採用されていくことになる
- css modules により、CSSがグローバルモジュールな問題が解決した
  - → これもその後は全面的に採用されている
- 初めて、モダンなフレームワークで同じやり方でフロントエンドをまとまった人数で開発できて、人数スケール性が高かった

### 抱えていた問題
- fluxつらい！
  - ボイラープレートが多い
  - 処理がイベントで切れてて追いづらい
  - modulesの切る単位が割と雰囲気で決まっており明確な基準がない
- パフォーマンスが出ないのでfluxを諦めて逸脱してる部分がある

など。 https://www.slideshare.net/_kondei/watch-mobx がわかりやすいので見てください。

## watch ページを生2からCCへ作り直し
プレーヤーで採用したfluxとか、一部のライブラリで採用したクリーンアーキテクチャ([持続可能な開発を目指す ~ ドメイン・ユースケース駆動（クリーンアーキテクチャ） + 単方向に制限した処理 + FRP](https://qiita.com/kondei/items/41c28674c1bfd4156186) で過去に考察した)の何がつらかったかを見返して、新たな設計をした結果「結局僕らのほしいのはReactiveなMVCだったんだ」ということが分かり、MobXを導入しました。

抱えていた問題と解決されたことなどについては [自分の記事](https://www.slideshare.net/_kondei/watch-mobx) がわかりやすいので見てください。

MobXを利用してどう具体的にどういうアーキテクチャで構築したかについて、このとき説明した構成と今もほとんど変わっていないですし、「正解パターン」だったと思っています。
実際、fluxのプレーヤーをこのやり方で作り直す予定です。

## 全ての入ったモノレポ化
モノリシックの反動であまりにもバラバラにリポジトリが20個以上乱立したせいで、管理がつらくなりました。

「フロントエンドの、ライブラリを除くPCのアプリーケーション部分全てを入れたモノレポを作って解決しよう！」という方針に一旦決まりましたが、試しに一部を入れてみてつらかった（後述）ので、やめることにしました。

## そして最新の方針へ
冒頭でも述べたように、最新の方針は以下のようになりました。

PC watchページのように開発が盛んなページを、以下の3層に分けた1個のリポジトリを作る。

- Front-End Server (FES)
- Container Component (CC)
- View Component (VC)

また、開発が落ち着いたページは、上の単位でひとまとまりとして更にまとめたモノレポ（各ページをgit submoduleとかsubtreeでまとめるだけのリポジトリでもいいかも）に置いて管理を楽にする。
図にするとこんな感じです。この他に各ページ共通のライブラリのリポジトリがたくさんありますが、割愛します。
![zu.png](https://qiita-image-store.s3.amazonaws.com/0/50442/faafdb55-59fd-9774-480a-23714c69007f.png)


### Front-End Server (FES)
  - バックエンドからページのための情報をかき集めるサーバー
  - CCが規定したPropsを渡して、Server Side Renderingする 
    - 絶対にDOMを持たないただのサーバーに徹する
  - docker imageを作ってdockerのクラスタ上に乗って動作

### Container Component (CC)
  - 状態管理、APIなどからデータ取得
  - VCにPropsを渡して描画
  - ページというアプリーケーションとしてVCを統合して動く
    - ページ全体がSSR時もCSR時も Isomorphic に動くように管理する
  - 絶対にサーバーサイドテンプレートのDOMをFESに管理させないでここで持つ
    - サーバーサイドテンプレートは最低限に留める
    - ページの中身のDOMはVCに委譲

### View Component (VC)
- いわゆるアプリーケーションの状態を持たない Presentational Component
- ページに描画される全ての部品のDOMをReactで持つ
- デザイン（css）を持つ
- 絶対にページの中身のDOMと見た目の管理をCCやFESにさせず、ここで持つ
  - 必要なものをすべてPropsでCCから要求する
- SSRできるように作る

これら3つに分けることによって、フロントエンドにおける「関心の分離の原則」と「単一責務の原則」が厳密に守られます。
また、SSRによるメリットをとるとサーバーとクライアントでコードの重複が発生する問題がありますが、 Isomorphic JavaScriptとして単一のコードで管理することで開発が楽になります。

また、この単位でのモノレポ化によって解決する問題は以下になります

## バラバラすぎるときに発生していた問題
- FES, CC, VCの互いの変更の取り込みが非効率になりがち
  - いちいちCIが終わってから、バージョン上げるだけのPR作るのつらい…
  - exportし忘れたせいでビルドできない、とか
- 共通で使うライブラリの依存を上げる作業が大変
- 同じページの変更なのに、リリースノートがFESとCCとVCで途切れてて追いづらい
  - FES, CC, VCそれぞれに同じfeatureブランチを作るのが面倒
- ページの中でバージョン違いのライブラリが混ざる
- リポジトリの切り方に一貫性がない
  - FES, CCはそれぞれページ単位、VCは全ページ入りだった
- 同じページを作っているのに、FES, CC, VC で割と互いに無関心になりやすい
- 1つのページで全技術スタック通して考える必要がある作業がやりづらい
  - パフォーマンス最適化など

## あまりにも全部入りにするとき発生する問題
- 生1、生2のモノリシックのつらみの再来
- CIが重すぎ (watchページのCCを入れただけでproduction buildが25分…)
- git-flow, github-flow, の両方が運用されていたが、リポジトリによって適切なものが違うのにどっちかに寄せなきゃいけない
- ページAの開発のせいでページBをコードフリーズする事態が生じる
- エンジニアチーム1, エンジニアチーム2, デザイナーチームで分担して開発を人数スケールさせづらい
- Separate Ways（別々の道）パターン に反する
  - https://www.ogis-ri.co.jp/otc/hiroba/technical/DDDEssence/chap3.html#SeparateWays
- 特定のページだけ別のやり方で作り直したくなったときに他のページと違ってつらくなる
  - 例えば、watchページだけ性能要件が厳しいので、今後watchだけ色々変わってくるみたいな例はありえる


## 余談：SPのプレーヤーとページ 
自分は開発者じゃないので詳細に触れないですが、開発しやすそうなので、これも一つの正解だと思います。

Smart Phone用の全ページがある単一リポジトリで管理されて、3,4人ほどで全ページが開発されています。
生成されるdocker imageも全ページ込み。
SSRもしています。
PCのページと違って、URLをまたいだSPAを実現しています。

### 技術スタック
- react
- redux-saga
- css modules
- ViewCompoent

# まとめ
どうしてもニコニコ生放送というサービスは巨大であり、開発者も多いので、モノリシックにしてもバラバラにしすぎてもつらい、という歴史が今まで辿られてきました。現在改めて見ると長い迷走に見えますが、その時その時でトレンドやサービスの事情が違ったりするので、関わった人はみんな当時にベストを尽くしたと思います。

最近思うことは、縦（機能）で切るとそこで人数でスケールアウトできるが、同じものを別々に実装したりして効率が悪くなる。逆に横（関心事や技術）で切ると、同じやり方で揃うので一人あたりの効率がスケールアップするが、どんどん増える開発者に仕事を任せてスケールアウトしにくい、というところです。どこまで分業すると生産性が上がるかを見極めて、良いとこ取りをしたいですね。

## 導入して効果のあった、重要なパラダイム
生放送で長いことかけて培ってきたフロントエンドのアーキテクチャに関わる知見として、以下のパラダイムが有用であったと思います。

- DDD
  - フロントエンドのアプリケーションでもドメイン層とUI層を分けよう
- マイクロサービス
  - 開発者が多くなったらフロントエンドを含むモノリシックにするのはやめようね
- マイクロフロントエンド
  - https://micro-frontends-japanese.org/
  - 完全に徹底するのは非効率だし無理だけど、縦に切るというエッセンスは重要
- Backends For Frontends (BFF)
  - バックエンドの影響から守られる
- TypeScriptによる型
  - ドメインモデルを型で表現する
  - Propsに型をつけることで、VC-CC間、CC-FES間のつなぎこみがスムーズになる
- Isomorphic JavaScript
  - DOMをクライアントサイドコードと共に一括管理でき、SSRもする1つの機能に対し修正箇所が2箇所にならずに済む
  - サーバーがサーバーに徹することができる
- reactive programming
  - 宣言的に書けて保守性が上がる
    - React.js
    - MobX
- モノレポ化
  - ライフサイクル・リリースサイクルが同じコード（1つのページとか）は同じ場所で管理すると効率が上がる
- 依存関係を一方向にする
  - 容易にリファクタ可能になる

# 関連する記事
- [ニコニコ生放送の watch ページを MobX で作り直している話](https://www.slideshare.net/_kondei/watch-mobx)
- [持続可能な開発を目指す ~ ドメイン・ユースケース駆動（クリーンアーキテクチャ） + 単方向に制限した処理 + FRP](https://qiita.com/kondei/items/41c28674c1bfd4156186)
- [React + TypeScript + CSS Modules によるコンポーネント指向フロントエンド開発の流れと知見](https://qiita.com/misuken/items/5ab7c7f47b245706a67c)
- [LayeredAtomicDesign でコンポーネントの粒度問題を解決する
](https://qiita.com/misuken/items/2902c85002e74559011f)
- [コンポーネント指向フロントエンド開発におけるデザイナーの参画について](https://qiita.com/ln-north/items/53c23beb3f82abf64a4c)
- [第1回　ニコ動／ニコ生 HTML5化への奮闘～ドワンゴ流動画配信サービスのつくりかた～](http://gihyo.jp/dev/serial/01/dwango-engineersoul/0001)