全てを書き換え続ける。N予備校Webフロントエンド実装6年のあゆみ

はじめに

ドワンゴ教育事業 Web フロントエンドチームの berlysia です。

ドワンゴ教育事業が提供するオンライン学習サービス『N予備校』は、この 4 月でリリース 6 周年を迎えました。N 予備校の Web フロントエンドはリリース以来、全面的な書き換えを行い、今も続けています。

この記事では書き換えに伴う N 予備校の Web フロントエンド実装の変遷を説明し、これら書き換えの経験やWebフロントエンドという領域の性質を踏まえて、すべてを書き換え続ける選択をしていることを述べます。

この記事は berlysia が他社様イベント*1にて発表させていただいた話題を元に再構成しています。

speakerdeck.com

JSConf JP 2021 で発表させていただいた事例とは異なるコードを対象にしています。

実装の 5 つの世代

自社他社を問わず数多の他のサービスと同じように、N 予備校の Web フロントエンドは様々な制約のもとで前進してきました。

実装の世代の推移の様子。変化点を取り出しているので横軸は一様になっていないが、全体が置き換えられていく様子が見える

v1

2016 年 4 月のファーストリリースに向けた開発で、諸々の事情により人海戦術で実装された、伝説のコードであると伝わっています。

次のような技術スタックからなる個別のページ群が、Ruby on Rails にべったりの形で書かれています:

  • Rails view 直書き
  • Page.js + jQuery
  • jQuery + CoffeeScript
  • React + Redux + Redux Thunk + 独自ミドルウェア

v1 は、2022 年 4 月現在ではかなりの割合で書き換えが完了しており、残している箇所も戦略的後回しによるものや、重要度の低い画面に限られています。

v2

リリース後すぐの 2016 年夏頃にかけて、v1 の技術構成の乱立状態から秩序あるスタックを見出そうとした実装が v2 です。次のような特徴があります:

  • React
  • RxJS によって Redux 風の独自 Flux 実装を用意
  • 実装内のモジュール同士の関係にも心を配った作り

v2実装の出現

最も複雑な画面の実装がこの v2 で行われリリースされましたが、すぐに v2 の開発は断念されました。当時徐々に Redux が筆頭の Flux 風実装になりつつあり、RxJS で独自実装したデファクトスタンダードから遠い構成は、理解が難しく属人性が高くなりすぎると判断されました。

v3

2022 年現在に連なる実装のベースが生まれたのが、リリース 1 年後の 2017 年 4 月のことです。この v3 という実装の特徴は次のようなものです:

  • React + Redux
  • Redux Observable
  • flowtype による静的解析

状態管理のために Redux を使い、WebSocket 通信の結果を扱うなど Action の時系列依存な操作を実装するために、ミドルウェアには Redux Observable を選んでいます。この実装に十分な能力があることを確認でき、実際に v2 実装が行われた一番複雑な画面の実装も置き換えられました。ここで v2 の実装は完全に姿を消します。

v3実装の出現とv2実装の滅亡

v3(TypeScript)

v3 の技術選定から 2018 年末頃にかけて、TypeScript 自体の進化や Babel の TypeScript プラグインが登場するといった変化の一方、v3 での実装が本格化するにつれて flowtype の開発体験に不満が高まっていきます。ライブラリの型定義がないことが多く、とくに Redux Observable から利用していた RxJS の型定義については、自分たちが利用するオペレーターが増えるたびに自ら型定義を実装する必要がありました。

2018 年末、年末年始でまとまった作業時間がとりやすいことを利用して、当時国内にも事例の少なかった flowtype から TypeScript への書き換えを行いました。およそ 2 週間で実装だけで 3 万行、テストも込みで 5 万行ほどが書き換えられました。

TypeScript化は2週間で、業務を止めて行われた

簡単なページでもボイラープレートが多い問題

v3 実装は見事にもっとも複雑な画面を実装してみせ、その能力を示したことでこの実装を全体に広げる方針になりましたが、他の画面の実装を進めるうちに妙な問題が見つかるようになってきました。Action や Reducer の実装を面倒がってか、不必要に状態を共有してしまうようになったのです。

Redux のよくある苦しみは、Action や Reducer といった複数の実装を用意しなければいけないことです。このあたりには Redux Toolkit などが取り組んでいますが、そもそも GET リクエストをしてその結果を受け取りたいだけの画面に、Action を中心にしたイベントソーシングによる予測容易性は過剰品質でした。

チャンク分割しづらい問題

コードが大きくなってくると、生成される JS バンドルを分割して必要に応じてロードされるようにして、ロード時間の改善を図りたくなります。Redux の Reducer や Redux Observable の Epic でもこの対応は不可能ではありませんが、一旦考慮なしに書かれてかつ不必要に Reducer 等が共有されているようなところから、綺麗に切り分けられるようにするのは現実的に難しいレベルの努力がいります。

動的ロードによって Reducer や Epic が拡張されるということは、素朴に書くとロードの前後で Store が持つ State の構造が変化するとか、Action に反応する Epic の依存関係を実装者が管理する必要があるとかで、検証しづらい独自の規約を持ち込む必要があるように思えました。

ページごとに今後も変化していきたい要求

いまは素朴な SPA(あるいは、これを目指している)として作られていますが、パフォーマンスのために SSR をするようになったり、ほかにも異なる道具や流儀に置き換えて行く可能性は、常に想定しています。サービスに対して Web フロントエンドが叶えられることを、自分たちの実装の都合で制約したくないからです。

ページ単位で独立に扱いやすい作りになるような実装パターンが定まり、知識をわざわざ入れなくても素直に書いてそのようになることを求めました。チャンク分割問題についても同じように素朴に解決したいです。

v3(React Query)

ここまでの話題をうけて、「シンプルな機能のページはシンプルに書けて、ページごとが容易かつ自然に独立して管理できる」ことが求められています。

また、2020 年末頃から匂わされていた React の Concurrent Rendering の流れに乗っていくには、React が非同期処理の状態を知る必要があるとわかっていました。このとき事前の試作により、Redux では素直な利用が難しいことがわかっていて、Suspense と書き味が合わせやすい道具が求められるともわかっています。

これらをうけて、React Query をつかったデータフェッチを中心に、Redux 非依存なページの実装パターンを打ち出しました。諸々の都合から少し時期が空いて、2021 年夏頃のことです。*2

React Queryを使った実装パターンを提示し始めた話

2022 年 4 月現在、エラー処理やキャッシュの扱いなどをチームで定めながら、ドメイン知識の移転や整理を兼ねたタスクとして、単純な画面はこのパターンで置き換えていっています。

Web フロントエンドは変更の要因が多い領域

Web フロントエンドの開発の上で、フロントエンドから見て外部要因で変更が起こるとき、その要因は例えば次のようなものが挙げられます:

  • ユーザーやビジネスの要求
    • フロントエンドに限らず、サービスに関わる全領域で共通しそうです
  • デザイン変更、API の変更、インフラ周りの変更
    • 隣接する業務領域の影響は当然受けて、フロントエンドは直接隣接する領域が多くなりがちです
  • ブラウザや Web 標準の変化
    • Web フロントエンドであれば求められます。iOS/Android でも似たことが言えそうです
    • WebView 等も絡んでくると余計にここは大きくなります
  • ライブラリの進化や陳腐化
    • バージョンアップへの追従、更新を終了するライブラリもあります
    • npm では顕著になっているように、OSS の報酬問題や情勢不安からくるサプライチェーン周りも課題です
  • セキュリティ、パフォーマンス、アクセシビリティ等の品質要求
    • バンドルサイズの肥大化や利用者属性の変化、脆弱性対応や予防など、多様なアクションがありえます

これだけの外部要因に応える必要がある*3のが当たり前で、これらの間にあって「現状維持」を選ぶだけでも、これらの要因に応えて変化していく必要があります。

その上でフロントエンドとして攻めた提案をしていくためには、先を見越して自ら変化を積み足さなければいけません。

変化し続けるために私たちが選んでいること

私たち N 予備校の Web フロントエンドチームでは、状況に応じて全てを書き換え続けることを選んでいます。*4

Martin Fowler 氏の「犠牲的アーキテクチャ日本語訳)」は、私たちの姿勢を説明するのに良い概念です。コードに寿命があることを認め、交換可能な作りを重視します。私たちは全体を過剰に枠にはめることなく、侵襲度合いの低い道具とデファクトスタンダードに近い構成を維持して、理解が容易で捨てやすい作りを維持しています。

この方針によって、全ての実装パターンが統一されていなくともよいというおおらかさも得られます。特定の箇所で必要に応じて特殊化する余地を持ちながら、良いパターンを全体に持ち込むことに価値があると判断すれば、その方向に全体を倒す余地も残しています。これは「どこかで試してみてだめならやめる」という試行も容易にするものです。

新しいパターンへの置き換えは、ドメイン知識の整理や移転のチャンスです。複雑になってしまった動作の整理をするタイミングにもなり、新しいメンバーには単純な画面を置き換えてもらって私たちのやり方に馴染んでもらうような役割も果たせます。*5*6

おわりに

オンライン学習サービス『N 予備校』の Web フロントエンド開発では、リリース以来 6 年の間に様々な変化と制約に晒されて、常に実装を変化させ続けることを選んできました。今できる最善のやり方を常に選び続けられるように、これからも変化を続けていきます。

We are hiring!

株式会社ドワンゴの教育事業では、一緒に未来の当たり前の教育をつくるメンバーを募集しています。

この記事で説明したような変化し続ける体制のためには、経緯や意志を残し伝え、常に考えながら自分たちのやり方自体を見直し続ける必要があります。書いて感じて考える Web フロントエンドエンジニアを求めています。

カジュアル面談も行っています。お気軽にご連絡ください。

カジュアル面談応募フォームはこちら

www.nnn.ed.nico

開発チームの取り組み、教育事業の今後については、他の記事や採用資料をご覧ください。

speakerdeck.com

*1: iCARE Dev Meetup #30 にて

*2:React Query以外にはSWRや、場合によってはRecoil, jotaiあたりも検討していましたが、いったんReact Queryを選んでいます。もしかしたらまた乗り換えるかもしれませんが、いまのところ欲しい性質は得られています

*3:フロントエンドエンジニアの価値はどこから生まれるのか の図を強く意識しています

*4:変化し続けるためには変化し続けなければならない(大真面目)

*5:不定期ながら一部や全体を置き換え、新しい良いパターンを探索・発見してメンテナビリティを高めながら、ドメイン知識の整理や移転も行えるという 3 点から、少し受動的ですが小さい単位での「ソフトウェアの式年遷宮」とも呼べるのではないでしょうか。

*6:個人的には学生時代からはてな界隈の方々を目にすることが多くて、式年遷宮の話したくなっちゃいがち