🏙️

Micro Frontendの勉強がてら、デモサイトを作ってみた

2025/01/01に公開

Micro Frontendについて気になったので、試し用のアプリ(デモ, コード)を作りながら勉強したものをまとめました。

ビデオ

ビデオでも紹介しています。Micro frontendの雰囲気がよりわかりやすいと思いますので、こちらもご覧ください。

https://www.youtube.com/watch?v=LjwsANjNFrs

Micro Frontendとは何か、メリットは何かの調査

結論からいうと、Micro Frontendとは下記の特徴を有するものです。

  • 一つのフロントエンドを複数の機能に分け、各機能は個別のチームがお互いに独立に開発・テスト・デプロイできます
  • 複数のmicro frontendを統合したコンテナアプリケーションが存在します。統合手法は複数あります

マイクロサービスと同様に、独立に開発・テスト・デプロイできることに主眼があり、そこにメリットを求めているようです。他のチームと連携を取らなくても、自チームのコードを自立して独立にデプロイできることがmicro frontendの試金石となります。

ただし流行り言葉に群がる人たちのせいで、上記に当てはまらないものもmicro frontendを名乗っている輩も登場しています。特にVercelのmicro frontendはコンテナアプリケーションがありませんので、micro frontendとは呼べないと思います。百歩譲ってmicro frontendと呼んだとしても、ビルド時に統合するものなので、開発やデプロイの独立性がないものになります。

参考資料については最後に紹介・解説しました

作ったもの

Micro frontendの議論が抽象的で、実際にどのようなメリットが得られるかが明確ではないと感じていました。特にVercelの話に惑わされて、micro frontendの良さがなんなのかが不明確でした。

そこで実際にmicro frontendを自作し、独立した開発・テスト・デプロイがどのようなものかを体験しました。

デモアプリはこちらからアクセスして、動作確認できます。コードはGitHubに用意しています。

Micro Frontendの紹介記事の例を参考に、自分なりにアレンジしたものです。

本当に独立に開発・デプロイできるのか?

上記のビデオで実証している通り、今回作ったものは、それぞれのmicro frontendが独自に開発・デプロイできます。

例えば宝石を取り扱う"react-site" micro frontendに変更を加えた場合でも、ここだけをデプロイすれば十分です。コンテナ("root-react" micro frontend)をデプロイし直す必要がありません。

また逆にコンテナ("root-react" micro frontend)に変更を加えた場合でも、ここだけをデプロイすれば十分です。宝石を取り扱う"react-site" micro frontendをデプロイし直す必要がありません。

ただし各micro frontendはURLを共通のステートとして持っていて、そこに依存関係があります。URLのルールを変えた場合はデプロイの順番等に注意を払う必要が出てきます。

デプロイの独自性はどのように担保しているか?

今回はデプロイの独立性を確保しつつ、4つのmicro frontendを1つのページに統合しています。これは4つのmicro frontendをビルド時につなげずに、ユーザがウェブページにアクセスしたときに初めて動的につながる仕組みを利用して実現しています。下記に紹介する「ランタイムにJavaScriptで統合する方法」です。

ユーザがコンテナmicro frontendにアクセスしたら、そこから個別のmicro frontendのJavaScript/CSS/HTMLを読み込み、コンテナアプリに埋め込みます。

開発時点やビルド時点では、他のmicro serviceに全く依存しないのがポイントです

どういう時に便利か?

下記のようなウェブサイトの場合、赤枠で示したような箇所は別の部署が担当することがあります。また制作に外注を使っている場合には、別の会社が別の国で作業しているケースすらあります。そうなるとお互いにコミュニケーションを取りながらデプロイのスケジュールを決めていくことは大変困難になってきます。

このようなケースでは各部署が独立にデプロイできるのは大きなメリットでしょう。

なお下記のウェブサイトの図は考え方の例として紹介しているもので、このウェブサイトの裏側がどうなっているかは私は知りません。ただし過去に制作に関わった大企業のウェブサイトやグローバル企業のウェブサイトでは、実際にこのようになっている例は珍しくありませんでした

欠点

Micro frontendの欠点については、このブログ記事が参考になります。

  • 性能の劣化: それぞれのMicro frontendは独立しているため、リソースを二重三重にリクエストしてしまうことがあります。またJavaScriptのバンドルが不必要に大きくなることがあります。(筆者注:リクエストウォーターフォールも発生しやすくなります)
  • 誤って全体に影響を与える: CSSの管理を誤ってしまうと、全体のレイアウトが崩れてしまう可能性があります(筆者注:iframe等であればCSSは全体にもれないのですが、その他のアプローチではリスクがあります)
  • ランタイムエラーの危険性: ランタイムにJavaScriptで統合する場合は、ビルド時にエラーが発生せずにランタイムにエラーが発生することになります

今回、工夫したところ

Hotwireでもいけることの確認

Wikipediaの記事では、CSRを使ったSPAに主眼が置かれています。ただし、Spotifyではiframeによるmicro frontendを使っていたことが知られており、CSR/SPAに限った技術ではないことは明白です。

今回はiframeではなく、ランタイムにJavaScriptで統合する方法であっても、Hotwireを使ったmicro frontendが構築できることを確認しました。ただし軽微な問題として、Turbo Frameはcross originだとCORS preflightが必要になります。

Hotwireは静的HTMLで書かれたウェブサイトに対応できますので、応用範囲が大きく広がります。

各micro frontendはRouterを持つ

各micro frontendを疎結合に保つ上で非常に大切なのは、共通したステートの管理です。martinfowler.comの記事にも記載されている通り、共通ステートはなるべく最小に止めるべきであり、そのメカニズムとしてURLは多くの利点を持っています。

今回もURLを使ってコンテナアプリと各micro frontendがステートを共有しています。

一般にRouterと呼ばれるものは、結局のところURLステートを解釈するツールです。そのため、URLステートを取り扱う各micro frontendには、それぞれに独自のrouterを持たせています。今回の場合はコンテナmicro frontendの"root-react"、宝石micro frontendの"react-site"、およびレストランmicro frontendの"hotwire-side"がrouterを持っています。

"root-react"および"react-site"側では自分で簡易的なルータを作っています。

"hotwire-side"のrouterはJavaScriptではなく、静的ファイルによるルータになります。

レイアウトシフト対応

複数のmicro frontendからデータを引っ張ってきますので、ロードされるタイミングがまちまちです。そのまま画面に表示していたら、レイアウトシフトが起こったりしますが、高級感を目指すウェブサイトの場合は品格を損なってしまいます。

そこで敢えて遅延を入れたり、画面切り替え時にアニメーションを入れています。レイアウトシフトを目立たせないための工夫です。

Thoughtworksの別記事では、micro frontendを支えるインフラの話がされていて、性能が低下することに言及し、対策を議論しています。今回はこのような根本策は行わず、見栄え上の対策のみを行なったと言えます。

Introducing micro frontends requires you to load the framework before loading the page, then load the target component and microapp. This will undoubtedly increase the loading burden. Taking largest-contentful-paint as an example, it is increased by about 0.2~1.2s without any performance optimization.

In order to reduce the loading time, we needed to increase the loading speed of each JS file through artifact minimization, tree shaking and caching at Cloudfront and browser level. We also needed to improve the parallel loading ability of JS as much as possible to ensure functionality.

これってGoogle Adsenseと同じ?

今回は「ランタイムにJavaScriptで統合する方法」を使用し、独立したデプロイを実現しました。しかしこの方法も新しいものではなく、昔からあるものです。

例えばGoogle広告を任意のウェブサイトに貼り付けるAdsenseサービスでは、Googleが用意した下図のコードを自分のウェブサイトに貼り付けだけで、広告のHTMLがダウンロードされて表示されます。「ランタイムにJavaScriptで統合する方法」と同じです。


https://www.seo-pro.jp/seo/adsense-how-to-paste

注意点 (デモと実際の運用で違ってくるところ、他)

  • 今回のデモではすべてのmicro frontendを一つのGitリポジトリで管理しています。実際に運用する際は別々のGitリポジトリになるでしょう。各micro frontend開発者が他のmicro frontendのリポジトリのアクセス権すら持っていないことも多いでしょう。
  • 解説動画では、ローカルの開発時にコンテナmicro frontendを動かしていますが、実際にはこれは不要でしょう。今回の宝石関連であれば"react-site" micro frontendだけで開発し、個別に動作確認もできます。完成したものをステージング環境にデプロイして、その時に初めて他のmicro frontendと組み合わせた全体の動作確認することが多いでしょう(この時にQAが見ることも多いでしょう)
  • デモのインフラは下記のものになります
    • 4つのmicro frontendはそれぞれViteでビルドし、Vercelにデプロイしました
    • Micro frontendでは開発時とデプロイ時のassetのhostが一定しないと面倒になります。そのためassetはすべて apache.castle104.com (さくらVPS上にKamal2でApacheをデプロイしたもの)に置きました。AWS S3でも良いです
  • 何箇所かCORS設定が必要になったところがありました

最後に

今回はmicro frontendを試しに作りながら、理解を深めていきました。また実際にmicro frontendが役に立ちそうな場面も理解できました。

Micro frontendが有効と思われるビジネスは容易に想像できますし、導入負担もそれほど大きくないと感じましたので、Thoughtworks Radarで"Adopt"(推奨)のラベルが付いたことも納得です。

最後に非常に大局的な自分の見方を紹介します。多数のファイルをウェブサーバにアップロードするだけの昔のウェブサイトであれば、そもそもが独立したデプロイを担保していました。そもそもフロントエンドがモノリス化したことは、JavaScriptが複雑になってビルドが必要になったことと強く繋がっていそうです。Micro frontendに分離する手法が、ビルド時の静的な結合をなくし、ランタイム時に動的に接続することだというのは、とても考えさせられました。

参考資料

Thoughtworks Radar

Thoughtworks Radarの内容をまとめると、以下のようになります。

  • 目的は独立にデプロイされ、独立にメンテナンスされるサービスの開発をスケールするのに有効
  • 一つの機能(feature)は、一つのチームがE2Eでオーナーシップを持つ
  • 各機能の開発・テスト・デプロイが独立に実施可能になることがゴール
  • 2016のRadarで初登場し、2020年まではポジティブに記載されていたが、以降は言及がない

Martinfowler.com

Martinfowler.comにはMicro Frontendの紹介記事があります。ここでは以下のことをが記載されています。

目的・利点

  • システムの一部だけを書き換える時に使用される。古いページの大半はそのままにし、新機能だけを新しい技術で作るときなど
  • 独立したデプロイが鍵である。他のサービスのことを考えずに各Micro frontendをデプロイすることが理想。各チームが独立にデプロイできなければならない
  • スタイルなど機能横断的なチームは不要。機能単位の縦割りチームが適切

技術要件

https://martinfowler.com/articles/micro-frontends/composition.png

  • 各ページにはそれを表示するためのmicro frontendが存在する
  • コンテナアプリ(Container application)が存在する
    • 共通のヘッダーやフッターを表示する
    • ページ横断的な機能である認証やナビゲーションを担当する
    • 各種のmicro frontendを呼び寄せ、いつ、どこに表示されるかを決定する

実装方法

  • Server Side Includes (SSI)を使った方法: SSIはウェブの黎明期から存在する非常に古い技術です。includeというタグを使って、サーバ側で親のHTMLファイルに子供のHTMLファイルを埋め込むことができます
  • ビルド時に統合する方法: 各micro frontendをパッケージとして発行し、コンテナアプリでこれを読み込む方法。これは良く見かけるものの、アンチパターンとして強く反対しています。個別のmicro frontendをデプロイするとき、コンテナアプリを含めて全てのmicro frontendをビルドしてリリースする必要があるからです
  • iframeを使った方法: HTMLの<iframe>タグを使う方法です。柔軟性で劣る面はありますが、概ねmicro frameworkの要件を満たします
  • ランタイムにJavaScriptで統合する方法: 最も柔軟で、よく利用されている方法です。デフォルトの選択肢です。各micro frontendがそれぞれ静的なSPAとして書かれていれば、コンテナアプリ側で<script>タグを使ってそのJavaScriptファイルを読み込めば、そのmicro frontendをマウントできます
  • Web Componentを使う方法: 基本的にはランタイムにJavaScriptで統合する方法と同じですが、Web Componentをインタフェースとして使います

他に面白いリンク (随時追加)

Discussion