最近、マイクロフロントエンドという技術を知っていろいろ調べていたら以下のような記事を見つけました。
マイクロフロントエンドとは何か、非常に分かりやすくまとまっています。
なので日本語で要約・加筆して後で読み返しやすくしていこうと思います。
マイクロフロントエンドとは
マイクロフロントエンドとは、 単体で実行可能な、サイトから切り出された特定のUI領域 であり、Webフロントエンドの新たなアーキテクチャです。
この特徴から以下のようなメリットがあります。
- インクリメンタルアップグレード
- シンプルで分離されたコードベース
- 独立したデプロイ
- 自立したチーム
インクリメンタルアップグレード
マイクロフロントエンドは単体で実行可能にするため、使用するフレームワーク、ライブラリが他のマイクロフロントエンドと共通ではありません。
このため、他のマイクロフロントエンドに左右されずに安定して依存するフレームワーク、ライブラリのバージョンをアップグレードすることが出来ます。
シンプルで分離されたコードベース
各マイクロフロントエンドのコードはモノリシックなフロントエンドコードに比べて格段にサイズが小さいです。
機能ごとにコンテキストを分けることで、開発時に認知しなければならない領域が小さくなり、開発者の負担が減ります。
独立したデプロイ
コンテキストごとに実行単位が分かれているため、独立してリリースすることが可能です。
独自のCI/CDパイプラインを構築し、他のマイクロフロントエンドのリリースサイクルを気にすることなくリリースすることが出来ます。
自立したチーム
コードとリリースサイクルが分離されることで、チームも機能単位で分離することが出来ます。
機能ベースのチームは顧客に価値提供するための必要なすべてを保有しており、独立して活動することが出来ます。
マイクロフロントエンドの実装
マイクロフロントエンドはどのように実装するのか、以下の観点で説明していきます。
- 統合アプローチ
- スタイリング
- 共有コンポーネントライブラリ
- クロスアプリケーション通信
- バックエンド通信
- テスト
統合アプローチ
マイクロフロントエンドは以下の機能を持つコンテナアプリケーションと呼ばれる領域に展開されます。
コンテナアプリケーションとマイクロフロントエンドはそれぞれ別のサーバーにホストされ、サーバーサイドインクルードなどの技術を用いて 、1つのサーバーが他のサーバーにリクエストを送信し、ページを構築します。
これは各マイクロフロントエンドの独立性を保つためです。
具体的なソースコード上での統合は以下のような手法があります。
- npmパッケージとしてビルド時に統合する
- iframeを介してランタイム時に統合する
- JavaScript処理でランタイム時に統合する
- Webコンポーネントを介してランタイム時に統合する
もっとも主流であるのは JavaScript処理でランタイム時に統合する 方法ですが、 統合プロセスにこだわりがない場合、 Webコンポーネントを活用してランタイム時に統合する 方法も良いとされています。
以下は 本家サイトにある実例 を少し弄ったものです。
<html> <head> <title>Feed me!</title> </head> <body> <h1>Welcome to Feed me!</h1> <!-- これらのバンドルは後続のJavaScriptによる統合により使用される --> <script src="https://browse.example.com/bundle.js"></script> <script src="https://order.example.com/bundle.js"></script> <script src="https://profile.example.com/bundle.js"></script> <div id="micro-frontend-root"></div> <!-- JavaScirpt処理での統合 --> <script type="text/javascript"> // バンドルにより関数が取り込まれる const microFrontendsByRoute = { '/': window.renderBrowseRestaurants, '/order-food': window.renderOrderFood, '/user-profile': window.renderUserProfile, }; const renderFunction = microFrontendsByRoute[window.location.pathname]; // divタグへレンダリングする renderFunction('micro-frontend-root'); </script> <!-- Webコンポーネントでの統合 --> <script type="text/javascript"> // バンドルによりWebコンポーネントが取り込まれる const webComponentsByRoute = { '/': 'micro-frontend-browse-restaurants', '/order-food': 'micro-frontend-order-food', '/user-profile': 'micro-frontend-user-profile', }; const webComponentType = webComponentsByRoute[window.location.pathname]; // JavaScript関数を用いてコンポーネント要素を配置する const root = document.getElementById('micro-frontend-root'); const webComponent = document.createElement(webComponentType); root.appendChild(webComponent); </script> </body> </html>
スタイリング
従来のモノリシックなフロントエンドでは、CSSの課題をBEMなどの厳密な命名規則やSASSのようなCSSプリプロセッサで解決してきました。
しかし、マイクロフロントエンドを活用したアプローチでは、 CSSモジュール や様々な CSS in JSライブラリ の活用により、プログラムから適用します。
また、Webコンポーネントによるアプローチを活用している場合、 シャドウDOM によるスタイルの分離も活用されます。
共有コンポーネントライブラリ
サイト全体の視覚的な一貫性を維持するため、再利用可能なUIコンポーネントのライブラリを開発する手法があります。
ラベル、ボタン、自動入力のドロップダウン検索フィールドなどのよく使われるコンポーネントを共有ライブラリとしてまとめます。
ただし、これらの共有ライブラリは以下の課題があるため、アプリケーション全体が成熟してから作成するのが良いです。
- アプリケーションの初期ではAPIのあるべき姿が安定しない
- 一貫性のないコードの寄せ集めになりやすい
- 明確な規則や技術的ビジョンがないため
- 汎用的なライブラリにするための強力な技術スキルが必要
- 多くのチーム間でのコラボレーションを促進するためのヒューマンスキルが必要
クロスアプリケーション通信
マイクロフロントエンドモジュール間でデータ通信をする方法は以下の3つあります。
- カスタムイベントにより上位のモジュールへ渡す
- 属性値として上位のモジュールから下位のモジュールへ渡す
- アドレスバーを使用する
ここで注意すべきはどのアプローチでも 状態が共有されないようにする 必要があります。
また、データ通信の仕組みが壊れていないことを自動的に確認する方法についての検討が必要です。
機能テストの実装は1つのアプローチですが、実装と保守のコストの観点からテスト数は制限した方がよいです。
コンシューマー主導のコントラクト の実装により、 各マイクロフロントエンドがほかのマイクロフロントエンドに必要なものを指定出来るようにすることも、1つのアプローチです。
バックエンド通信
マイクロフロントエンドのバックエンド通信のパターンとして BFFパターン があります。
BFFパターン(Backend For Frontends)とは、複数のAPIの集約をになったり、 フロントエンドアプリケーションのためのビジネスロジックを実行したりするバックエンド実装のパターンです。
マイクロフロントエンドが通信するAPIが複数あったり、各APIの定義・実装が不安定であったりする場合にとても効果的なパターンです。
テスト
注意すべき点として、各マイクロフロントエンドとコンテナアプリケーションの統合テストがあります。
CypressやSeleniumなどのe2eテストライブラリ・ツールにより実施出来ます。
しかし、決して網羅しようとせず、単体テストでチェックできない観点に対して実施します。
例
コンテナアプリケーションでマイクロフロントエンドが描画されているか確認するために、 ハードコードされたタイトル文字列が存在することをアサーションする
マイクロフロントエンドをまたがるシナリオのテストもフロントエンドの統合の検証に焦点を当て、 ユニットテストでカバーされている内容など、必要以上にテストしません。
欠点
マイクロフロントエンドも、他のアーキテクチャ同様、そのメリットはトレードオフです。
それは次のような欠点があります。
- ペイロードサイズ
- 環境の違い
- 運用とガバナンスの複雑さ
ペイロードサイズ
独立して構築されたJavaScriptバンドルは一般的な依存関係の重複を引き起こします。
例えば、すべてのマイクロフロントエンドにReactの独自コピーが含まれる場合、ユーザーにはReactをn回ダウンロードさせることになります。
都市部よりはるかに遅いネットワーク環境で実行される場合、ダウンロードサイズを気にする必要があります。
しかしこれは、初期ページロードとの引き換えとなります。
初期ロードで一度に必要なファイルを一括ダウンロードするモノリシックなフロントエンドに比べ、 必要なタイミングで段階的にJavaScriptバンドルをダウンロードするマイクロフロントエンドは読み込みが速くなる可能性があります。
環境の違い
マイクロフロントエンドはコンテナアプリケーションではなく、空白ページでスタンドアロン実行が出来る場合があります。
コンテナアプリケーションにレガシーコードが残されているなど、技術的な難がある場合に有効な開発手段です。
しかし、同時に本番環境とスタンドアロン実行環境に大きな差がある場合にリスクが伴います。
ここでの対策は他の状況と同様、可能な限り本番環境に似た環境へ定期的にデプロイし、統合時の問題を早くキャッチすることです。
運用とガバナンスの複雑さ
純粋に1つの大きなものを小さい複数のものに分割して管理するため、管理するものが増えます。
より多くのリポジトリ、ツール、ビルド/デプロイパイプライン、サーバー、ドメインなど、考慮するものがたくさんあります。
これらの考慮を実現していくために必要な技術的・組織的な成熟度があるかどうかを検討する必要性があります。
所感
今回はマイクロフロントエンドという新しいアーキテクチャについてキャッチアップしました。
アプリケーションが解決すべき問題は段々複雑化し、それに伴ってアーキテクチャも複雑化していくのを感じ取りました。
如何に大きな問題を小さく扱うかという、マイクロフロントエンドのヒントは私が普段携わっているWindowsアプリケーションにも応用が利くかなと感じました。
今回は省略しましたが、元記事には具体的な実装の説明もあるのでご一読されることをおすすめします。