🐝

精読「マイクロサービスアーキテクチャ 第2版」(第三部 人 - 第14章 UI)

2025/01/06に公開


マイクロサービスアーキテクチャ 第2版
マイクロサービスの設計、実装、運用に必要なベストプラクティスや最新技術を解説した、実践的なガイドブックです。これを読めば、マイクロサービスに関してそれっぽい会話もできますよ。

関連記事

デジタルへ向けて

デジタル化に向けたアプローチとして、組織がWebとモバイルを別々に扱うのではなく、より統合的に考えるべき。顧客が製品を利用する最適な方法は予測できないため、マイクロサービスが提供するような細かい粒度のAPIを採用し、さまざまなデバイスや実店舗を通じて顧客に異なる体験を提供することが重要。

UIは、ユーザに提供したいさまざまな機能を組み合わせていく場所であり、この問題を組織的な側面と技術的な側面の2つの観点から考える必要がある。組織的には、UI提供に関して誰がどのような責務を負うのかを検討し、技術的には、そのインターフェースを実装するためのパターンを取り上げていく。

所有権モデル

UIの所有権に関する2つのモデルがある。従来の階層型アーキテクチャでは、複数のチームが関与し、変更に時間がかかる。対照的に、UIとバックエンドを1つのチームが管理することで、迅速な変更とエンドユーザーとの直接的な対話が可能になる。

しかし、マイクロサービス企業では、フロントエンド専任のチームが一般的。

専任フロントエンドチームに向かう要因

専任フロントエンドチームが求められる要因は主に3つ。まず、UIの提供には専門的なスキルが必要で、これを持つ人材は少なく、集めるのが難しいため、専任チームが必要。次に、一貫性を保つために、1チームでUIを担当することが求められる。最後に、特にシングルページアプリケーション(SPA)では、技術的に分割が難しく、専任チームによる取り組みが効果的。

ストリームあらインドチームを目指して

専任フロントエンドチームを持つことは、優れたスループットを目指す場合には適切ではないと考える。理由は、新たな引き継ぎ点が生まれ、速度が低下するため。理想的には、エンドツーエンドの機能スライスに基づいてチームを構成することで、各チームが迅速に新機能をデリバリできるようになる。

Matthew SkeltonとManuel Paisが提案する「ストリームアラインドチーム」モデルでは、価値のある仕事の流れに沿って働くチームを構成し、他のチームへの引き継ぎなしで効率的に進めることが推奨されている。このようなチームは、ユーザーと直接的な関係を築くことができ、ユーザーのニーズを満たすことに集中できるため、より高い成果を上げることができる。

専門家の共有

専門家をチームに配置することは、スキル共有や効率的な作業を実現するために重要。特に、UI開発やインタラクションデザインなどの特定の専門分野では、高度なスキルを持つ人材が不足しており、そのようなスキルを持つ人はしばしば過負荷状態になる。従来の専任チームでは、スキルセットが限定されるため、他の開発者がそのスキルを学ぶ機会を失う可能性がある。例えば、iOSやAndroid開発のエキスパートは、他のメンバーにその技術を教えながら、難易度の高いタスクに集中できる環境を作ることが重要。

また、UIやデータベースなどの専門分野でのスキルを共有するためには、実践コミュニティを立ち上げ、横断的に知識や経験を交換することが有効。これにより、専門家の貴重な時間を最も重要なタスクに集中させつつ、他のチームメンバーがスキルを身につける手助けができる。さらに、Team Topologiesにおける「イネイブリングチーム」のように、専門家が他のチームに支援を提供し、スキルを他のメンバーに伝える役割を担うモデルも有効。

一貫性の補償

一貫性は、UI開発において非常に重要な要素です。専任のフロントエンドチームを設置することで、同じデザイン言語やインタラクションのルールを保ち、一貫性のあるユーザー体験を提供できる。これには、共通のCSSスタイルガイドやUIコンポーネントの作成が有効。また、イネイブリングチームを利用して、異なるチーム間でUIの一貫性を確保することができる。例えば、フィナンシャル・タイムズのOrigamiチームは、ブランドアイデンティティを反映したWebコンポーネントを構築し、ストリームアラインドチーム間で一貫したデザインを維持している。

ただし、一貫性を追求することが常に最適であるとは限らない。例えば、AmazonのAWSでは、製品間で一貫性がなく、異なるスタイルやインタラクションが使われているが、これはチームの自律性を高めるために意図的に行われている。このようなアプローチは、ユーザー体験の一貫性が欠けることがある一方で、デリバリー速度の向上をもたらし、結果的に市場での競争力を高めることに寄与している。

結局のところ、一貫性の追求とチームの自律性のバランスを取ることが重要。どちらを優先するかは、プロジェクトの目的や組織の方針に依存するため、慎重な判断が求められる。

技術的課題の克服

UI開発は、緑色の画面のターミナルからリッチなデスクトップアプリケーション、Web、モバイル体験へ進化してきた。しかし、基本的なUIコンポーネント(ボタン、チェックボックス、フォームなど)はほとんど変わらず、新たな技術(SPA)やモバイルデバイスによる複雑さが増している。ユーザーは、どのデバイスを使ってもシームレスな操作を求めており、UIのモジュール化と統合方法を再考する必要がある。

パターン:モノリシックフロントエンド

「モノリシックフロントエンド」パターンでは、すべてのUI状態と振る舞いがUI内で定義され、必要なデータを取得するためにマイクロサービスを呼び出す。例えば、アルバムやトラックリストの情報を表示するために、UIがAlbumサービスにリクエストを送り、Promotionsサービスから最新のセール情報を取得する。これは、モノリシックSPAを構築する際に最も一般的なモデルで、フロントエンド専任のチームが関与する。マイクロサービスは、UIが解釈可能な形式(通常はJSON)でデータを提供し、UIはそれに基づいてコンポーネントを作成し、バックエンドと同期を取る。

モノリシックフロントエンドを使う場合

モノリシックフロントエンドアーキテクチャにはいくつかの欠点がある。まず、専任のフロントエンドチームに向けた構成となり、複数のチームが責任を共有するのは難しくなることがある。また、さまざまなデバイスに対応するためのレスポンス調整が難しく、例えばモバイルクライアントが表示できる情報量に制限がある場合、必要ないデータを取得することになる。この問題への解決策として、UIがリクエスト時に取得するフィールドを指定する方法があるが、これにはマイクロサービスがこの形式に対応している必要がある。

このような問題を解決するために、GraphQLやBFF(Backend For Frontend)パターンを活用する方法が有効。また、モノリシックフロントエンドは、UIの実装とその振る舞いを1つのデプロイ可能ユニットに格納したい場合に最も効果的だが、複数のチームが関わる場合、階層的なアーキテクチャや組織的サイロに陥る可能性があるため、その点には注意が必要。

パターン:マイクロフロントエンド

マイクロフロントエンドアーキテクチャは、フロントエンドの異なる部分を独立して作業し、デプロイすることができる組織的なアプローチ。Cam Jacksonによる定義では、マイクロフロントエンドは「独立してデリバリできるフロントエンドアプリケーションが、より優れた全体に構成されるアーキテクチャスタイル」とされている。このアーキテクチャは、バックエンドのマイクロサービスと同様に、フロントエンドも独立してデプロイできるため、ストリームアラインドチームにとって重要なパターンとなる。

マイクロフロントエンドは、SPA(シングルページアプリケーション)によるモノリシックなJavaScriptを多用するWeb UIが引き起こした課題に対応するために人気を集めている。異なるチームがフロントエンドの異なる部分を担当し、変更を加えることができるため、各チームは他のチームから独立して作業を進めることが可能になる。

実装

マイクロフロントエンドパターンの実装には、主に2つの分解テクニックが使われる

  • ウィジェットベースの分解
    フロントエンドのさまざまな部分をつなげて、1つの画面を作成する。このアプローチでは、個々のコンポーネントが統合されて、ユーザーが一貫した体験を得られるように設計される。

  • ページベースの分解
    フロントエンドを独立したWebページに分割する。各ページは、独立してデプロイ可能で、異なるチームがそれぞれの部分を担当する。

どちらのアプローチも検討する価値があり、それぞれにメリットが存在する。

マイクロフロントエンドを使う場合

マイクロフロントエンドは、エンドツーエンドのストリームアラインドチームを採用し、階層型アーキテクチャから離れる際に重要。また、フロントエンドが大規模になり複数の専任チームが必要な場合にも有効。しかし、マイクロサービスがウィジェットやページに適合しない場合があり、例えば動的なレコメンデーションや先行入力機能など、横断的な対話が必要になると、このモデルの適合性は低くなる。

パターン:ページベースの分解

ページベースの分解では、UIを複数のWebページに分け、各マイクロサービスが異なるページを提供する。例えば、「/albums/」はAlbumsサービスが処理し、在庫情報はInventoryサービスが提供する。この構成により、チームはエンドツーエンドでUIをレンダリングし、変更の影響を把握しやすくなる。技術的にシンプルで、クライアントデバイスに応じた適応も容易。

ページベースの分解の用途

ページベースの分解は、モノリシックなフロントエンドやマイクロフロントエンドの両方に役立ち、特にWebサイトのUI分解の際にはデフォルトの選択肢。Webページという単位は、Web全体の中核的な概念であり、大規模なWeb UIを分割するための簡単で自然な手法。しかし、SPA技術が普及する中で、ページベースのUIが減少していることが問題。ページベースの分解は他のパターンと組み合わせて使用することも可能で、例えばウィジェットを含むページを作成することができる。

パターン:ウィジェットベースの分解

ウィジェットベースの分解では、独立して変更可能なウィジェットがGUIに組み込まれ、複数のウィジェットがユーザーインターフェースを構成する。例えば、MusicCorpのフロントエンドにはショッピングカートやレコメンデーションウィジェットが含まれ、ユーザーがウィジェットとの対話を通じて、マイクロサービス(例: Recommendations、Wishlist)を呼び出す。各ウィジェットを提供するチームは、サポートするマイクロサービスを所有し、一貫性を持たせることができる。このパターンは、Spotifyなどの実世界のアプリケーションにも多く見られ、ウィジェットが状況に応じて組み合わされ、様々な方法で活用される。

実装

ウィジェットベースのUI実装では、ウィジェットが個別に機能し、それらを一つのWebページで統合する。この方法では、簡単なWebサイトではクライアント側やサーバ側のテンプレートを使ってHTMLフラグメントとしてウィジェットを組み込むことができる。しかし、ウィジェットの振る舞いが複雑になると、他のUI部分と衝突しないように工夫が必要。例えば、異なるバージョンのReactを使用しているウィジェット同士が干渉しないようにするために、ウィジェットごとに異なるSPAフレームワークを使うことがある。

また、ウィジェット間での通信には、カスタムイベントを発行する方法が有効。例えば、チャートウィジェットで選択されたアルバムに基づき、他のウィジェット(レコメンデーションやアルバム詳細ウィジェット)が更新される。このようなカスタムイベントは、ブラウザ内で発生するイベント駆動通信として、マイクロサービス間のイベント駆動通信に類似している。

ウィジェットベースの分解を使う場合

ウィジェットベースの分解は、複数のチームが同じUIに貢献するのを容易にし、柔軟性を高める。SPA環境では、ウィジェットを使ってマイクロフロントエンドを実現するのが効果的だが、ページのペイロードサイズの増加や依存関係の管理が課題となる。シンプルなウィジェットでは、テンプレートを利用して簡単に組み込める。

制約

ソフトウェアのユーザーはさまざまなデバイスで対話するため、それぞれのデバイスに対応した制約を考慮する必要がある。例えば、Webアプリケーションではブラウザや解像度、モバイルアプリではネットワーク制限やバッテリー消費に配慮する必要がある。また、アクセシビリティの考慮が必要であり、法律的にも障害者向けのアクセスを保障する規制がある。クライアント側でのフィルタリングやデータの集約を通じて、デバイスごとのニーズに対応する方法が求められる。

パターン:中央集約ゲートウェイ

中央集約ゲートウェイは、外部UIと下流マイクロサービスの間に位置し、UI向けに呼び出しのフィルタリングや集約を行う。複数の呼び出しが必要な場合、UI側でデータを個別に取得し、不要なデータを捨てることになるが、中央集約ゲートウェイを利用することで、UIからは単一の呼び出しで必要なデータを取得できる。この方法により、帯域幅の節約やアプリケーションの遅延改善が可能になる。

所有権

中央集約ゲートウェイは、UIとマイクロサービスの呼び出し集約やフィルタリングを行うが、これにより所有権の問題が発生する可能性がある。UIチームがゲートウェイを所有するのが自然だが、専任のフロントエンドチームにはバックエンドコンポーネントの構築スキルがない場合がある。また、中央ゲートウェイがデリバリのボトルネックになることもあり、複数のチームが変更を加える場合は調整が必要で、開発が遅くなる。

この問題を解決するために、BFF(Backend for Frontend)パターンが有効であるとされている。

各種のUI

中央集約ゲートウェイは、モバイルデバイスとデスクトップの異なるニーズをサポートしつつ、呼び出し集約とフィルタリングを行う。しかし、モバイルデバイスでは、画面サイズやリソース制限から少ないデータしか表示できず、接続の多さがバッテリーやデータプランに影響を与える。そのため、モバイルアプリケーションは少数の呼び出しと異なるデータ表示を必要とし、APIバックエンドに機能追加が求められる。

異なるUI(モバイルアプリ、Webサイト、管理インターフェース)をサポートすることで、ゲートウェイが肥大化する可能性があり、複数チームが同じデプロイユニットで作業することで、ゲートウェイがボトルネックになるリスクも存在する。

複数の懸念

API呼び出しの処理には多くの懸念が伴う。APIゲートウェイは、APIキー管理、ユーザー認証、呼び出しルーティングなどの一般的な懸念を解決できるが、カスタマイズには注意が必要。サードパーティ製品を使用する場合、製品固有のDSL(ドメイン固有言語)で設定を行うことが多く、開発者が慣れている言語やプラクティスを使えないことがある。これにより、後でシステムを移行する際に問題が発生する可能性もある。

また、集約ゲートウェイが複雑化し、専任チームが管理することになると、さらに問題が発生する。新機能をロールアウトする際には、フロントエンドチーム、ゲートウェイチーム、マイクロサービスチームとの調整が必要になり、デリバリーの遅延が発生することもある。

そのため、専用のAPIゲートウェイを使う場合は慎重に検討し、フィルタリングや集約ロジックを別の場所に分けることを積極的に考えるべき。

中央集約ゲートウェイを使う場合

単一の中央集約ゲートウェイを使用する場合、その所有権が1つのチームに集中していれば、特に問題はない。むしろ、UIとバックエンドの調和が取れ、集約点が不要になることもある。しかし、複数のチームが関与するデリバリ組織では、中央ゲートウェイを使用することで調整の必要が増え、問題が発生する可能性がある。

そのため、バックエンドでの呼び出しフィルタリングや集約はUIのユーザー体験を最適化する上で重要であり、慎重に機能を制限して組み込む必要がある。もし中央ゲートウェイの所有権モデルに関連する問題を解決したい場合、BFF(Backend for Frontend) パターンを活用することが解決策となる。このパターンにより、特定のUIに最適化されたバックエンドを提供し、中央ゲートウェイによる調整の負担を軽減できる。

パターン:BFF(フロントエンド向けのバックエンド)

BFF(Backend for Frontend)パターンは、特定のUI向けに開発されたバックエンドを提供するアーキテクチャ。中央集約ゲートウェイとの主な違いは、BFFが単一目的に特化しており、特定のUIのニーズに応じた集約バックエンドを提供すること。このアプローチは、UIのさまざまな懸念に効果的に対応し、開発チームがUIとBFFを一緒に管理することで、ボトルネックを回避することができる。

BFFパターンでは、UIとBFFが密に結びついているため、開発チームはUIの要件に合わせてAPIを柔軟に定義・適応させやすく、クライアントとサーバ両方のコンポーネントのリリース調整が簡素化される。また、BFFは各UIに特化しているため、異なるUIのために異なる集約バックエンドを提供することが可能。

例えば、SoundCloudやREAなどの企業では、BFFパターンがうまく機能している。これにより、UIに対する集約処理を独立して行うことができ、中央集約ゲートウェイを使う際の調整の煩雑さやボトルネックを回避できる。

BFFをいくつにするか

BFF(Backend for Frontend)の構成方法に関して、2つの異なるアプローチが考えられる。一般的には、異なるクライアントごとに1つのBFFを持つことが推奨されている。このモデルは、REAで実際に使用されている方法。REAでは、iOSとAndroidの各アプリケーションがそれぞれ独自のBFFを持ち、UIの機能を提供している。この方法では、UIごとに特化したBFFを持つことにより、UIの異なるニーズに柔軟に対応できる。

一方、SoundCloudでは、iOSとAndroidの両方のリスナーアプリケーションが同じBFFを使用している。これは、共通するUI要素が多いため、1つのBFFで対応可能だと判断されたため。ただし、このアプローチでは、複数のクライアントに対応するため、BFFが複雑になり、肥大化するリスクがある。

この場合、重要なのは、BFFが「同じ種類のUI用」であれば、異なるクライアントが同じBFFを使用しても問題はないという点。例えば、iOSとAndroidのネイティブアプリケーションが同じBFFを使うことは可能だが、他のネイティブアプリケーションには別のBFFを使うべき。

組織構造やチームの責任範囲もBFFの数や設計に影響する。1つのチームがiOSとAndroidのアプリケーションを担当する場合は、同じBFFを使うことが合理的だが、別々のチームがそれぞれ担当している場合は、別々のBFFを作成することが望ましい。このように、BFFの数を決める際には、チーム構造や管理のしやすさが重要な要因となる。

REAのケースでは、iOSとAndroidの体験が類似している場合には1つのBFFを使い、異なる場合には別々のBFFを使うというアプローチが採られた。重要なのは、ソフトウェアがチームの境界に沿って機能することが多いため、BFFの設計もチームの構成に基づいて決めるべきだという点。

再利用とBFF

BFF(Backend for Frontend)間で再利用する際の懸念として、コードの重複が生じる可能性がある。具体的には、同じ集約処理や下流サービスとのインタフェースで類似のコードが多くなることが挙げられる。共通機能を抽出することは有益だが、重複を発見し、適切に抽象化することが課題となる場合がある。

共通コードの再利用方法には2つの選択肢がある。1つ目は共有ライブラリを作成する方法で、コストが低く簡便だが、サービス間の結合が強くなるリスクがある。2つ目は、新たなマイクロサービスとして共通機能を抽出する方法で、特にビジネスドメイン機能に関連する場合に効果的。選択肢の選定は、コストやコードの再利用箇所数に基づいて判断するべき。

例として、顧客のウィッシュリスト表示を挙げると、BFFが複数回同じマイクロサービスを呼び出す状況では、共通の振る舞いを抽出し、専用のマイクロサービスを作成することが重複を減らす方法として有効。ただし、サービスの抽出コストやコードの再利用頻度を慎重に検討する必要がある。

デスクトップWebなどのためのBFF

BFF(Backend for Frontend)は、モバイルデバイスの制約を解決するだけでなく、デスクトップWebにも有用な場合がある。通常、Webアプリケーションは強力なデバイスで動作し、複数の下流サービス呼び出しを直接行えるため、BFFが必ずしも必要ではない。しかし、Web UIの大部分がサーバーサイドで作成されている場合(例:サーバーサイドテンプレートを使用する場合)、BFFはその作業を実行するための適切な場所となる。このアプローチでは、BFFの前にリバースプロキシを配置し、呼び出し結果をキャッシュすることができ、効率を高めることが可能。

さらに、外部関係者用のBFFを公開する事例もある。例えば、ロイヤリティ支払情報を抽出できるようにする、あるいはストリーミングを許可するためにBFFを使用することがある。この場合、外部関係者はUIを使用しないため、実際にはBFFとは呼ばないことになるが、同じパターンが適用されることがよくある。外部関係者がAPI呼び出しの利用や変更を制限されている場合、このアプローチは非常に効果的。BFFを利用することで、破壊的変更の影響を最小限に抑え、外部APIの旧バージョンを維持する必要性を減少させる。

BFFを使う場合

BFFは、Web UIの集約が必要な場合に有効で、モバイルUIや第三者に特定の機能を提供する際には積極的に検討すべき。UI開発者とサービス開発者が分離している場合、BFFの利用はさらに効果的。追加サービスのコストを考慮しつつ、BFFは関心事の分離を実現するため、説得力のある選択肢となる。

次に、BFFの実装方法やGraphQLの役割について考える。

GraphQL

GraphQLは、クライアントが必要なデータを柔軟に取得できるクエリ言語。クライアントは必要なフィールドだけを指定してデータを取得でき、余計な情報を返さないため効率的。GraphQLを用いることで、複数のサービスからデータを集約し、API呼び出しの回数を減らすことができる。また、クエリの変更により、集約やフィルタリングを簡単に変更でき、サーバー側の変更を避けることができる。これにより、BFFの実装にも適用可能で、クライアント側の柔軟性を高める。

ハイブリッドなアプローチ

ハイブリッドなアプローチでは、Webサイトでウィジェットベースの分解アプローチを採用しつつ、モバイルアプリケーションにBFFアプローチを使用する場合がある。この場合、重要なのはユーザーに提供する基盤となる機能の凝集度を維持すること。例えば、音楽の注文や顧客詳細の変更に関連するロジックは、各サービス内で完結し、システム全体に広がらないようにする必要がある。また、中間レイヤーに多くの振る舞いを詰め込みすぎないようにすることが求められ、バランスを取ることが重要。

まとめ

これまで示したように、機能分解はサーバ側に留まる必要はなく、専任のフロントエンドチームを持つことは不可避ではない。

参考

Discussion