Mercari Engineering Blog

We're the software engineers behind Mercari. Check out our blog to see the tech that powers our marketplace.

フロントエンドエンジニアは Micro Frontends の夢を見るか

Mercari Advent Calendar 2018 の6日目はフロントエンドチームの @vwxyutarooo がお送りします。
このタイトルが言いたくて Micro Frontends の記事を書きました。皆さんは Micro Frontends という言葉を聞いたことがあるでしょうか? 私は数ヶ月前まで全く知りませんでした。メルカリのフロントエンドチームにて Micro Frontends に関して考える機会があったので、Micro Frontends とはなんなのか。何をどのように解決しようとしているのかという内容を紹介します。

Micro Frontends とは

Micro Frontends という考え方は ThoughtWorks Technology Radar にて2016年に初めて登場したと言われています。日本語で言うときは複数形は無視して "マイクロフロントエンド" と言うことが多いようです。

Micro Frontends が登場した背景として、近年アプリケーションは複雑、肥大化しているケースが多いです。
超絶複雑、或いは巨大でなくとも、開発速度を上げるためにエンジニア組織を大きくするというアプローチが取られます。しかし、エンジニア組織において人数が増えれば開発速度が上がるという構図は結構早い段階で成立しなくなり調整などの部分に殆どのリソースが食われることになります。
サーバサイドにおいては Microservices というアプローチによりこの問題を解決しようとしています。これに対しフロントエンドでは BFF などによってクライアントのビジネスロジックやデータモデルを切り出すなどのアプローチはあります。しかしフロントエンドのアプリケーションはモノリシックなまま、柔軟性やチームのスケールなどの面で追従できていないという問題がありました。
そこでバックエンドとフロントエンド混合の Microservices チームを構成して、各 Microservices が UI を返したらよいのでは、というアイディアです。

f:id:vwxyutarooo:20181206021034j:plain:w300 f:id:vwxyutarooo:20181206021021j:plain:w600

比較的古いアーキテクチャのままになってしまっているなど幾つかの要素が影響してはいますが、実際私達のメルカリ Web でも同様の問題を抱えています。また組織の拡大に伴い、この問題はより顕著になってきています。

Micro Frontends の基本的な考え方については以下の記事およびスライドにて詳しく解説されていますので、もっと詳しく知りたい方は是非こちらを読んでみてください。特に Micro Frontends: Building a modern webapp with multiple teams というスライドには Micro Frontends に関する基本的なアイディアがとても分かりやすくまとめられています。

Micro Frontends の考え方

Micro Frontends の基本的な考えは micro-frontends.orgマイクロフロントエンドの根底にある考えセクションに詳しく書かれています。
特に使う技術やコードなどの面でそれぞれチーム毎に独立させようという点が重要です。これを次の3つに区分して Micro Frontends 3原則と (私が) 勝手に呼んでいます。

技術

各チームは技術において他チームの影響を受けません。
これはフレームワークに関しても同様で、各チームは React や Angular、Vue.js などそれぞれに適したフレームワークを使うことができます。
現実世界においてこれは JS ファイルの肥大化など幾つかの問題を生みますが、現段階においては考え方として受け取ってください。

コードやルール

技術同様、実際のコードも共有しません。また、状態やグローバル変数、コーディングルールなども互いに依存しないよう独立して機能させます。
同じチーム内であっても兄弟関係にあるコンポーネンは独立している必要があります。
また、互いのコンフリクトを避けるため、チームの Prefix を定めて管理することが推奨されています。

デプロイ

micro-frontends.org では定められていませんが、実際のところの共通認識になっているように思います。これができていないと多チームの中でデプロイの順番やリバートなどが複雑化し、実はリポジトリを分割しただけだった、という事にもなりえます。
それなりにハードルの高い部分になるので、もしこれが実現できないとしても、チーム分けや実装が独立してデプロイ可能かという指標を持つことが重要だと思っています。
また、ビルドハッシュを持たせたバンドルを使いつつデプロイを独立させる方法について manifest.json を使った方法が後に紹介する Micro Frontends の理論と実践
 -価値提供を高速化する真のマイクロサービスのあり方 のスライドで紹介されています。

Micro Frontends の実現方法

現時点で Micro Frontends を実現する方法は iframe か Web Components (Custom Elements) の2択です。iframe を使いたい人が今の所存在しないので React や Angular と Custom Elements でどこまでできるか、というのが話の焦点になってきます。

大前提として Micro Frontends のパーツだけで成立させるのではなく、統合レイヤーのようなものが必ず存在します。この統合レイヤーにて SPA などの機能を持たせたり、各チームから提供される Micro Frontend を乗せていきます。
この統合レイヤーはプレーンな HTML で表現されていることが多いですが、React、Angular、Vue.js などで作られた SPA に対してそれぞれ Custom Elements を媒介に Micro Frontends を載せることができるようです。私達が採用している React でも公式のドキュメントで紹介されています。ざっくり次のような形になります。

まず Custom Elements を作ります。この時 Custom Elements に React の VirtualDOM を生やすことが可能です。

import React from 'react';
import ReactDOM from 'react-dom';

const TextComponent = ({ children }) => {
  return (
    <div>
      <p className="test-component">{children}</p>
    </div>
  );
};

class CustomText extends HTMLElement {
  connectedCallback() {
    const mountPoint = document.createElement('span');
    this.attachShadow({ mode: 'open' }).appendChild(mountPoint);

    const name = this.getAttribute('name');
    const text = this.getAttribute('text');
    const url = 'https://www.google.com/search?q=' + encodeURIComponent(name);

    ReactDOM.render(
      <>
        <a href={url}>{name}</a>
        <TextComponent>{text}</TextComponent>
      </>,
      mountPoint,
    );
  }
}

customElements.define('custom-text', CustomText);

あとは Custom Elements ファイルを登録すれば JSX 内で使えるようになります。

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
        <custom-text name="Mercari" text="Micro Frontends"></custom-text>
      </div>
    );
  }
}

export default App;

また、各フレームワークの対応状況は Custom Elements Everywhere というサイトで確認することができます。React に関しては100%対応できていない項目があるため注意が必要です。

提供されるコンポーネントはそれぞれ独立されていることが条件でした。そのためデータのやり取りが必要な場合には PubSub 形式によるデータのやり取りが推奨されています。各チームにある フレームワークなどの差異は Custom Elements で吸収しようというのが micro-frontends.org の考え方です。
Custom Elements はクライアントサイドでのレンダリングになるため、SPA 同様初期レンダリングの遅延や、SEO 観点での対策が必要となります。特に現在の Google クローラは Chrome 41 相当の機能しか持たないため、Custom Elements v1 は非対応です。
この問題に対して前述の Micro Frontends: Building a modern webapp with multiple teams では SSI による解決方法が紹介されています。

Micro Frontends の事例

Micro Frontends の事例として有名なのが Spotify と IKEA です。国内では FiNC、Kaizen Platform、FOLIO などの企業が Micro Frontends に取り組んでいる (取り組もうとしている) ようです。

Spotify

f:id:vwxyutarooo:20181206003948p:plain:w600
From Slide Share

Spotify では UI のパーツ (或いは Function) 単位でチームが分けられています。実際に彼らの Web アプリケーションを見てみると、レイアウトになるベースアプリケーション、サイドバー、フッターのナビゲーションで CSS の命名ルールなどが異なっているのを確認できます。 初期段階では iframe と postMessage によって Micro Frontends が実現されていました。しかし現在は iframe の使用を取りやめ、全て React Redux に移行しているとのことです。Twitter 上でその内容について多少コメントがあります。 iframe を使っていたときはデプロイまで完全に独立して行えていると聞いていましたが、現在は1つのバンドルとしてリリースされているようです。

Micro Frontends においては各 UI の見た目的な整合性を取るという役割が非常に重要になってきます。Spotify では Shared Frameworks としてガイドラインや UI コンポーネントを提供しています。結合時に全体の整合性をチェックするチームも存在していて、UI の整合性を取るための工夫が必要になることが分かります。 Spotify の Microservices と Micro Frontends に対するトライは下記の How Spotify Builds Products (Organization. Architecture, Autonomy, Accountability) というスライドで詳しく説明されています。

FiNC

FiNC では React と Custom Elements を用いた Micro Frontends を取り入れているそうです。各 Bundle の集め方など実際にぶつかる課題やその解決方法などが上記のスライドにまとめられいます。

メルカリ Web から見る Micro Frontends

メルカリ Web に Micro Frontends が必要となりそうな理由は独立したチームとデプロイです。組織が大きくなっていくと、徐々に開発からリリースまでのスピードが上がらなくなってきます。それを解消するためにチームとコードを小さく分け、各自がオーナーシップを持てる粒度にしたいという考え方は Microservices に対するそれと同じです。 Microservices と大きく違うのは、Micro Frontends では UI や ビルド、バンドルという概念と、最終的に相当数の Microservices を1つの画面に組み上げる必要があるという点です。
後に説明していますが、チームが拡大する過渡期において、Micro Frontends 本来の考え方によるチーム分割がうまくワークしないケースも多々あると思っています。
結果、チームの切り分け方が一番重要で難しい要素になると考えています。
チームの分け方について世の中の事例や私達が議論しているもので次の4つの方法があります。

Microservice 単位

Micro Frontends の分け方です。本来であればこのパターンでチームを分けるべきところですが、私達のチームにとっては現実世界に落とし込むには結構難しい方法でした。
Micro Frontends の説明をする上で、図やサンプル上では Microservice と1:1の関係で描かれている部分は多いのですが、実際にはそこまで綺麗に別れないケースも存在します。他にも1つの Micro Frontend チームから遠く離れたあちこちに UI を提供する必要があるために統合レイヤーが無駄に複雑になってしまうという問題が頻繁に発生しそうです。
また、メルカリのフロントエンド組織において Microservices 単位で無理にチームを切り出そうとすると、組織に対してチーム数が多くなりすぎてしまいます。Microservires に追従するには組織が小さく、モノリスで進めるには組織が大きすぎる、という問題は Micro Frontends を取り入れようとした時に結構起こり得る構図かもしれません。

UI パーツ単位

先に紹介した Spotify のパターンだと認識しています。注意すべきなのは、Spotify のアプリケーションにはページという概念が限りなく無いに近いです。そのためサイドバーやフッターのプレイングバーなどは1回しか使われないことが保証されており、複数箇所から参照されることがありません。よってページ毎に微妙に異なるというケースなども想定する必要が無いです。なので、Spotify はこのパターンでとても分割しやすいアプリケーションだということです。
この方法では音楽再生などバックエンドが関係ない部分も切り出していけるという点が大きいです。

対してメルカリの Web を UI パーツで切り出そうとした時に幾つか課題が生じます。例えばそれぞれ Role の重みがパーツによって大きく異なってしまいなかなか適切な粒度のチームが作れないということ。また、同じ様なパーツでもページ毎に微妙に異なる仕様を持っていてチーム分けの定義にいちいち迷ってしまう。実装においても React における Props のようなインタフェースが外部チームと依存関係を持ってしまうなどのような課題が多く挙げられました。
挙がった課題の量に対して切り出せるチームの形や数にあまり利点を見いだせなかったため、このパターンはメルカリの Web には適さないだろうと考えています。

ページ単位

前述2つの単位の課題からチームで挙がった案です。しかしこの方法でも幾つか問題が発生します。一番大きな問題として、明らかに分ける必要の無いページが頻出します。例えばメルカリで言う、ブランドカテゴリのページです。これらのページを束ねて1つのチームにする必要はありそうです。その点整理できればこれはメルカリ Web にとって現実的な案と言えそうです。 結果生まれる課題として、単位が不明瞭であることや、UI 毎の同様にチーム毎に規模や重さが大きく異なってしまう。そして何よりも運用時においてメリットのあるチームがイメージできないなどの点が挙がっています。

デプロイ単位

上記3つのパターンがそれぞれ組織と課題に合わないのでは、というところから、デプロイを独立させたいかどうかという観点だけでチームを決定できないか? という案です。デプロイ単位という呼び方が適切かは怪しいですが、前述の3つのパターンが全てありえます。ただのハイブリッドと言ってもいいかもしれません。
そのため1つの Micro Frontend チームが複数の Microservice を参照することも起こりえます。この状態を Micro Frontends と呼ぶのかと言われると正直分かりませんし、それなりに反対意見も出る気がしています。そもそも Micro Frontend のアイディアが Microservice と1:1のであるところからスタートしているので、アンチパターンとして名を残す可能性もあります。
段階的な Micro Frontends へのアプローチとして捉えることもできますが、Microservice と Micro Frontends が1:1でない時、チーム構成以外にどの様な問題が起こるかという点においてもう少し議論が必要です。

まとめ

Micro Frontends がどの様なものか、私たちの案含め幾つか派生の考えなども含め広く浅く紹介しました。Micro Frontends 自体まだ十分に試行錯誤されているものではなく、また Custom Elements などの技術も発展途上です。そのため micro-frontends.org にある内容を全て満たそうとしてもなかなか現実にマッチしません。根底にある考え方を踏襲し、自分たちのプロダクトやチームの抱える問題に合わせて必要な要素を組み合わせることでその問題を解決できる可能性があるかもしれないと考えています。
メルカリのフロントエンドチーム内で Micro Frontends が本当に今の私達の課題を解決する最適な手段なのか。そうだったとしてどのように導入すべきかという点でまだ議論をしているところです。

課題や難しい話はたくさんありますが、純粋に考えて、クライアントのコードを分割し、チームから技術、ルール、デプロイなどが独立して可能になるのであれば最高の世界です。
現時点ではまだ不完全ではありますが、Micro Frontends が普通に実現できる世界を夢見つつ、引き続きインプットや検証、検討をしていきたいと思っています。

明日7日目の執筆担当は @AHA_oretama です。引き続きお楽しみください。

PR

このようにメルカリでは、チームメンバーひとりひとりがオーナーシップを持ちながらスケールできるフロントエンドを目指しています。 このようなチームを率いてみたいエンジニアリングマネージャーの方は是非ご応募ください!

Links