2月12日、CI/CDプラットフォームであるDaggerが「ReactフロントエンドをGoとWebAssemblyで置き換えました(We Replaced Our React Frontend with Go and WebAssembly)」と題した記事を公開した。この記事では、GoとWebAssemblyを用いて新しいUIを構築した経緯や狙い、さらに得られた成果について詳しく紹介されている。以下に、その内容を紹介する。
2つのコードベース = 作業の重複と機能の制限
Daggerでは、DAG(有向非巡回グラフ)の操作をリアルタイムで可視化するため、2種類のインターフェースが提供されてきた。1つはGoで実装されたターミナルUI(TUI)であり、もう1つはオンラインダッシュボードとしてReactを用いて実装されたDagger Cloudである。
しかし、以下のような問題が生じていた。
- 同じ機能を二重に実装する必要があり、開発効率が低下していた
- リアルタイムで大量のデータを処理するReactベースのWeb UIがパフォーマンス面で問題を抱えていた
開発チームは、これらを解消する新たなアプローチを検討した。
新アプローチの目標
新しいDagger Cloudを構築するにあたって、主に次の2つの目標を掲げた。
- コードベースを統一し、重複をなくして新機能の開発速度を向上させる
- ターミナルUIに匹敵するスピードとパフォーマンスを備えた軽快なWeb UIを実現する
GoとWebAssemblyの採用
チームは上記の目標を達成する手段として、Go言語でWebAssemblyアプリケーションを作ることを選択した。Goを選んだ背景としては、もともとチームにGoエンジニアが多く、ターミナルのUIもGoで書かれていたため、1つの言語に統一するほうが保守・拡張しやすいという利点があった。
ただし、GoとWebAssemblyの組み合わせには以下のような課題もあった。
- ReactなどのJavaScriptフレームワークに比べ、Go + WebAssembly向けのUIコンポーネントライブラリや開発ツールが十分に成熟していない
- ブラウザでのWebAssembly実行には2GBのメモリ上限があり、大規模データの扱いにおいて最適化が必須
実装のリスク低減
Go + WebAssemblyはまだ主流ではないため、チーム内でも懸念があった。特に、以下のポイントを中心にプロトタイプを作成し、リスク評価と検証を行った。
- Go-appフレームワークの活用により、PWA(Progressive Web App)としてUIを構築できるか
- TailwindやAuth0など外部サービスとの連携、および大量のコンポーネントを同時に更新するケースへの対応
- メモリ使用量(特に2GBの制限)の問題をどう解決するか
プロトタイプ段階でこうした問題を洗い出し、最も大きな課題はやはりメモリ使用量であることがわかった。それ以外の部分については、Go言語の公式ドキュメントやGo-appのドキュメントなどが充実していたこともあり、大きな障壁はなかったという。
プロトタイプから本番へ
プロトタイプが完成すると、「awesome wasm」と呼ばれる本格的な開発プロジェクトが始まった。その過程で得られた主な知見は以下の通りである。
- メモリ使用量の削減が最優先課題で、何十万行ものログをブラウザで表示してもクラッシュしないよう、仮想端末レンダリングライブラリを最適化した。この最適化はTUIにも恩恵をもたらした。
- GoでのJSONパースが遅く、大量のデータを処理するときに問題があったため、WebSocketとエンコーディング方式に[encoding/gob](https://pkg.go.dev/encoding/gob)を使うアーキテクチャへと変更した。
- WebAssemblyファイルのサイズは初期で約32MBあったが、[Brotli圧縮](https://github.com/google/brotli)を導入することで約4.6MBまで削減した。
- Go-appでUIコンポーネントを一から構築する作業は思ったほど困難ではなかった。リアルタイム更新の処理も含め、柔軟に最適化を行えるのが利点である。
- 一部のnpmパッケージを利用するために、[Browserifyを使ったGoモジュール](https://daggerverse.dev/mod/github.com/vito/daggerverse/browserify@d368836636284116d090e271742904fea369cf72)を開発し、JavaScriptコードを取り込めるしくみを実装した。
- ブラウザ標準のReact DevToolsのようなツールはないが、Goのpprofやブラウザのデフォルトプロファイラを活用してCPU・メモリ使用状況を分析できた。
- Go-appでPWAとして実装されているため、Dagger Cloudをネイティブアプリのようにインストールし、フルスクリーンやデスクトップのタスクバーから起動することが可能になった。
こうした取り組みを経て、DaggerはDagger Commanders向けに新しいDagger Cloud v3を先行公開し、その後一般向けにも提供を開始した。
メリット
ReactからGo + WebAssemblyへの移行によって、UIの応答性が向上し、大規模かつ複雑なトレース表示でも安定したパフォーマンスが得られるようになった。
またエンジニアリング面でも、Web UIとターミナルUIが共通のコードを使うことで、最適化を一度行うだけで双方に反映されるという効率性向上が大きなメリットである。
採用を検討すべきか
Daggerのチームは、誰にでもGoでフロントエンドを構築することを推奨しているわけではないとしている。あくまで以下のような条件に合致する場合には有力な選択肢になるという立場である。
- Goを得意とするエンジニアがチームに多い
- 複雑なUIをTypeScript/Reactで実装した際にパフォーマンス面で問題が生じている
- 複数のUIを言語・フレームワークの垣根を超えて共通化したい
- 開発速度を上げる必要がある
上記に当てはまらない場合は、Reactなど既存のJavaScriptフレームワークの採用が適切だと強調している。なお、Dagger Cloud v3はまだベータ版だが、既に一般公開されており、フィードバックも受け付けているとのことだ。
詳細はWe Replaced Our React Frontend with Go and WebAssemblyを参照していただきたい。