Skip to content

Instantly share code, notes, and snippets.

@pirosikick
Created June 28, 2019 02:16
Show Gist options
  • Save pirosikick/140f9bd85f3ecb6edae3c2d84c362c1f to your computer and use it in GitHub Desktop.
Save pirosikick/140f9bd85f3ecb6edae3c2d84c362c1f to your computer and use it in GitHub Desktop.

Micro Frontends

https://martinfowler.com/articles/micro-frontends.html

micro frontendの利点

  • Incremental upgrades
    • micro frontendではそれぞれのパーツに対して意思決定できるし、それぞれのパーツが理にかなうと思ったタイミングでアーキテクチャや依存関係、UXのアップグレードが可能
  • Simple, decoupled codebases
    • 単一のモノリスなフロントエンドよりソースコードが小さくなり、シンプルで開発者が取り組みやすくなる
    • 明示的に境界づけられたコンテキストを引くことで、パーツ同士の意図しない結合を防ぐ。また、パーツ同士のデータやイベントのがどのように流れているかが明示的・意図的になることを後押ししてくれる。
  • Independent deployment
    • microserviceと同様にデプロイの独立性は鍵。デプロイの範囲が小さくなることで、関連するリスクも小さくなる。そうなるように、それぞれのmicro frontendが独自のCI/CDのパイプラインを持つべき
    • 隣のチームが中途半端なものや壊れた機能をプッシュしたりしても影響を受けないようにする
    • 特定のmicro frontendをビルド・管理しているチームが本番環境にリリースしてOKと思ったタイミングでリリースできるようにする
  • Autonomous teams(自立したチーム)
    • コードベースとリリースサイクルを分離することで、独立したチームになるための一歩を踏み出せる。チームに独立してユーザーに価値を届けるために必要なオーナーシップの全てを持つことで、すばやく、効率的に動けるようになる
    • そのためには技術ベースではなくビジネスの機能ベースでチームを分けるべき。特定のページ(機能)のE2Eを一つのチームで持つべき

端的に言うと、micro frontendとは、デカくて恐ろしいものをより小さく・管理しやすいピースに分けて、そのときにそのピース同士の依存関係をはっきりさせるということ。

統合のアプローチ

  • micro frontendを実現するアプローチの紹介と、それぞれのトレードオフについて。
  • Container Application
    • どのアプローチにも共通するパターン:複数のmicro frontend + Container Application
    • 役割
      • ヘッダーやフッターなど共通の要素を表示
      • 横断的な認証やナビゲーション等を担当
      • それぞれのmicro frontendを持ってきて、描画するタイミングや場所を知らせる
  • アプローチ
    • サーバーサイドでSSIを使って統合
    • ビルドタイムに統合
      • 各micro frontendがnpmパッケージを公開、Container Applicationの依存にそれらを含め、Container Applicationのビルド時に統合
      • micro frontendのリリースに、Container Applicationのリリースが必要になるのでこのアプローチはよくない。実行時に統合する方法を模索すべき
    • 実行時に統合
      • iframeを使う
        • 簡単にできるけど、いろいろとアレ
      • JSで動的に統合
        • 拡張性が高い
      • Web Components
        • JSとあまり変わらないが、コンポーネントのAPIを統一する必要があり、JSより拡張性がない

JSを使って実行時に動的に統合

  • 各micro frontendのJSをscriptタグでロードして、それらが提供しているグローバル関数を実行するとそれぞれのエントリポイントが描画される
    <script type="text/javascript">
      // These global functions are attached to window by the above scripts
      const microFrontendsByRoute = {
        '/': window.renderBrowseRestaurants,
        '/order-food': window.renderOrderFood,
        '/user-profile': window.renderUserProfile,
      };
      const renderFunction = microFrontendsByRoute[window.location.pathname];

      // Having determined the entry-point function, we now call it,
      // giving it the ID of the element where it should render itself
      renderFunction('micro-frontend-root');
    </script>
  • 拡張性が高い
    • 必要なタイミングでJSをロードしたり、グローバル関数に引数を渡すことで挙動を変えるなど可能
  • 各micro frontendのbundleされたJSを各自リリースすればいいだけで、Container Applicationを変更せずにリリース可能

スタイル

  • BEMやCSS Modules、CSS in JS、shadow DOM等を使いましょう
  • 開発者が別々にスタイルを定義できて、統合した時に正しく表示できていることが保証できるのであれば、どの手法を選んでも問題なし

共通コンポーネント

  • micro frontend間でUIの一貫性を保つために共通コンポーネントを定義したくなるかもしれないが、うまくやるのが結構難しい
  • 最初に全部カバーする巨大なフレームワークを作る誘惑に駆られがちだが、どのように使われるかの想定が出来ていない時に作るのはよくない
  • コードの重複を許容して各micro frontendで独立して開発し、パターンが見つかった時に共通化するのがよい
  • アイコンやボタンなどの基礎的なパーツや、オートコンプリートやドロップダウンフィールドなど複雑なUIロジックを含むパーツは共有してもよいかも。ただし、それらにビジネスロジックを含めないように注意。
  • 共通パーツを誰(どのチーム)が所有・管理していくかは、みんなでコントリビュートできて、かつ、管理人(または管理チーム)が品質や一貫性を保証できる形がよい
    • みんなで所有・管理するパターンは「誰も所有・管理しない」と一緒で、品質・一貫性が犠牲になる
    • 中央集権オンリーにすると、コミュニケーションが不十分で、使われないライブラリになる
    • 2つのバランスを取った方式がグッド

アプリケーション同士のコミュニケーション

  • アプリケーション同士のコミュニケーションはよくない結合を生むので、出来る限り少なくすべきだが、多少は必要になってくる
  • カスタムイベントは間接的にやり取りでき、直接的な結合を最小限にできる良い方法だが、micro frontend間の契約を明確にしたり強制するのが難しい(型的な話かな?)
  • React的な上流→下流はデータを渡して、下流→上流はコールバックで受け取るモデルが、契約がより明示的になるのでよい
  • どのアプローチを選択しても、状態を共有することは防ぐ。状態を共有すると次はデータ構造やドメインモデルを共有することになり、結合度が高まる。
  • うまくいくアプローチは色々あるが、最も重要なのは、どのような結合を生み出しているか、どうやってmicro frontend間の契約を維持するかをよく考えること。
  • また、どうやって統合が壊れていないかを自動的に検証するか考えるべき。
    • 機能テストは実装とメンテのコストが高くつくので、数は多く作れない
    • 代わりに、コンシューマー駆動契約がよい

バックエンドとのコミュニケーション

  • 新機能を加えるたびにバックエンドの変更が必要な場合は、BFFを導入し1チームでmicro frontendからBFFまでを受け持つべき
  • 認証はContainer Applicationで受け持って、トークンをmicro frontendの初期化時に渡す。

テスト

  • Container Applicationのe2eテストは、各micro frontendの統合がうまく行っているかの確認をする。各micro frontendの内部のビジネスロジックが正しいのかは、各micro frontendでテストすべき

サンプル

  • https://demo.microfrontends.com/
  • React
  • MicroFrontendコンポーネント
    • componentDidMountで各micro frontendのbundleをダウンロード
    • bundleのonloadが発火したら、初期化関数を実行
  • Container Applicationからhistoryの引き継ぎ
    • micro frontend内で他のページに遷移すると、Container Applicationのルーターが該当するmicro frontendを表示する
  • webpackのexternalsを使って、グローバルに定義しているReactとReactDOMを全micro frontendで共有

micro frontendの欠点

  • ペイロードサイズ
    • 共通の依存パッケージがあっても、独立してビルドしているのでそれぞれのJSバンドルで重複して持つことになってしまう。
    • サンプルのように、共通の依存関係はバージョン単位で揃えて、webpackのexternalsでグローバル変数渡しする
    • それでも、code-splittingされていないレガシーなモノリシックSPAよりは初期ロードする容量は少なくなるかも。
  • 環境の差異
    • 開発時はスタンドアローンモードで独立した環境でチェックするが、本番環境で統合された時に動かないみたいなことが起きがち
    • テストでなるべく早い段階で壊れていることを検知できるようにする
  • 運用や統治の複雑さが増す
    • リポジトリ、CIのパイプライン、ドメインなどがmicro frontendの数分、増える
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment