JX通信社CDOの小笠原(@yamitzky)です。
JX通信社は「今起きていることを明らかにする報道機関」というミッションの元に、新型コロナリアルタイムダッシュボードを 2月16日 から提供し続けています。今回は、「新型コロナプロジェクト」の発足から現在に至るまでの、プロジェクトの進化についてご紹介します。
プロジェクト発足
そもそものプロジェクトの発足としては、2月14日の下記のツイートが発端でした。およそ 2 日でリリースしたことになります。
日本の感染者増えているので、もうそろそろ中国がやってる地域別まとめページ作っても良いのでは?https://t.co/6gSg3s16Nw pic.twitter.com/TyNeP44NbO
— けろっと (@kerotto) 2020年2月14日
当時は、東京都のような自治体公式の特設サイトや、国内全体の動向をまとめたサイトはほぼありませんでした。国内の公共機関・報道機関のなかで、かなり速くリリースできた部類に該当するかと思います。
フェーズ1: Vue.js でのプロトタイピング
現在は React で作られているプロジェクトですが、当初のプロトタイピングフェーズでは Vue.js を使っていました。
Vue.js は Progressive(漸進的) なウェブフロントフレームワークです。最もシンプルなのは、次のような単体の HTML として配信する方法です。この形式ではトランスパイル(Webpack によるビルド等)の必要もなく、HTML 単体を配信するだけでも SPA になり、非常に手早くプロジェクトを開始できます。
// index.html <html> <body> <div id="app">{{ message }}</div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> new Vue({ el: "#app", data: () => ({ message: "hi" }) }) </script> </body> </html>
今回のプロジェクトでは「なるべく早くリリースする」という目的があり、ツール周りでつまずきたくなかったので、まずは Vue.js で雑にプロトタイピングしました。
フェーズ2: React × TypeScript × Emotion × Parcel への移行]
プロトタイプとして実現したものの、NewsDigest のウェブメディアで配信したいという課題がありました。NewsDigest の CMS は、Django (Python) で管理しており、必ずしも SPA との相性が良いとは言えません。
そこで次のような形で、 CMS にたった 2 行のタグを埋め込むだけで新型コロナのダッシュボードを配信できるようにしてみました*1。
技術的な必要条件としては、
- 1 つの script タグに JavaScript の依存関係を全て詰め込む
- 1 つの script タグさえ埋め込めば、デザイン(stylesheet) も再現できる
です。これを最短で実現するため、バンドラー(Webpackの代替)として Parcel を利用しました。Parcel はほぼ設定不要で使えるバンドラーなので、今回のような急ぎのプロジェクトにピッタリです。また、CSS ファイルに依存せずデザインを指定するため、Emotion も使っています。Emotion は React と相性の良い TypeScript ベースのデザイン用ライブラリで、JS 内にデザイン指定を埋め込めます。
// index.tsx const Wrapper = styled.div({ padding: 8, color: 'red', display: 'flex', // vendor prefix も自動付与 }) export const Component = () => <Wrapper>some content</Wrapper>
つまり、プロトタイピングから本番配信までの間に、Vue.js から React・TypeScript にまるっと置き換えたことになります*2。
フェーズ3: gitでのデータ管理から、サーバーレスなデータ管理へ
新型コロナの感染者数字は、リアルタイムに更新されます。実は、プロジェクト初期ではデータベースを用意しておらず、TypeScript のソースコード内に直書きする形でデータ管理をしていました。数値更新するたびに GitLab CI が動いて、JS ファイル自体をデプロイしていたのです・・・!
// 実際の data.ts export const data = { modified: '2020.02.19 11:30', japanStats: { infected: 616, infectedChange: 96, dead: 1, deadChange: 0, // クルーズ船(ダイヤモンド・プリンセス号) cruiseInfected: 542, cruiseInfectedChange: 0 }, ... }
さすがに、機械化しづらい、型安全でなく事故が起こりやすい、git でデータ更新をするのは属人性が高い、Python 製の FASTALERT API としての提供が難しい... という事情もあり、Google Spreadsheet をデータベースとして利用し、JSON から使えるようにしました。また、データ更新の仕組みは GitLab CI ではなく、AWS Lambda でサーバーレスな形で動かすようにしています。
フェーズ4: Next.js での SSR への移行
<script>
タグでの配信は、SEO 的に弱い可能性があるのではないか、という懸念が生まれました。そこで当初の Parcel から、Next.js へ移行しました。Next.js はReact のフレームワークで、サーバーサイドレンダリングの機能などが組み込まれています。
この Next.js のサーバーは、Amazon ECS(Fargate) 上にデプロイしています。
フェーズ5: ウィジェットとしての外部提供と yarn monorepo 化
JX 通信社が提供している新型コロナダッシュボードは、メドピア社やLINE社にウィジェットとしても提供しています。また、NewsDigest だけでなく FASTALERT 内でも配信しています。NewsDigest での提供、ウィジェットとしての提供、FASTALERT 内での提供、、、これらを 1 プロジェクトでやるのは現実的ではなかったため、次のような monorepo 構成を行っています*3。
@corona/components ・・・ 各種グラフを提供するライブラリ。FASTALERT からも npm install している @corona/server ・・・ @corona/components に依存する、next.js のプロジェクト。newsdigest.jp 用 @corona/widget ・・・ corona/components に依存する、webpack のプロジェクト。ウィジェット配信用
この monorepo 構成は、yarn の workspace 機能を使っています。
また、@corona/server と @corona/widget では、ビルドツールなども異なっています。これは、プロジェクトの目的や、求められる安定性*4などに応じて、意図的に使い分けたものです。
うまく monorepo 構成にすれば、コードを使いまわしつつ、最適な技術選定ができるようになります。
フェーズ6: グラフライブラリの移行
プロジェクト当初は、chart.js と d3 などを使っていましたが、 vx という React 向けのグラフライブラリに移行しました。
Chart.js は canvas ベースのグラフライブラリです。しかし、 React のような宣言的な UI の思想との相性の悪さや、サーバーサイドレンダリングできない、柔軟にカスタマイズできないなどの課題がありました。
d3.js もデータ可視化に使っていましたが、厳密には「ドキュメントをデータに基づいて操作・構築するためのライブラリ」です。雑に言えば「DOMを操作するためのライブラリ」なので、 React(React-DOM)のような DOM 操作のライブラリと、役割的にかぶっています。
そこで、 React ベースの低レイヤーなデータ可視化ライブラリである vx に移行し、React だけで SVG での可視化をしています。
- React 的な、宣言的データ可視化の実現
- TypeScript の型
- 柔軟なデータ可視化の実現
などができるようになりました。一方で、パフォーマンスチューニングや、コード量の増加などは起きています。
まとめ
新型コロナウイルスに関しては、社会的な需要や、刻々と変わる状況などを踏まえ、かなりスピード重視でプロジェクトが発足しました。当初は git でデータ管理していたほど、一般的なアプリケーション構成のセオリーからは外れた作り方です。
一方で、新型コロナダッシュボード提供開始から3ヶ月経ち、ビジネスの状況に応じて大規模なアーキテクチャ変更(式年遷宮)を行っており*5、「ビジネスとスピードと品質の両立」も実現できたと思います。これらの両立のポイントは「ビジネス要件に合わせて技術を使いこなし、いかに漸進的に進めるか」です。
今回のプロジェクトでは、かなりインターン生に協力いただいています。本当にありがとうございます! 引き続き、フロントエンドのエンジニアインターンを募集中です(まだ新型コロナは収束していないのでリモート中心です)。ぜひ一緒に、インパクトのある開発をしましょう!
*1:この JS 自体は、Amazon S3 と CloudFront で配信しています
*2:同じようなことを実現する方法は他にもあります。この技術選定は、慣れの部分が大きいです
*3:データ更新用サーバーレスパッケージなども含めると、10 個ほどパッケージが含まれています
*4:ウィジェットは絶対にサーバーを落としたくないため、SSR は行っていません。また、顧客ごとにカスタマイズする関係で SSG にもしていません
*5:プロジェクト発足から 3 ヶ月ぐらいしか経っていないのに、1000行以上差分のあるプルリクが複数あります。同僚から「走りながら車輪を全交換した話」というタイトル案が出るくらいです