Amazon Web Services ブログ

Backends for Frontends パターン

このブログでは、Backend for Frontend パターンを実装し、マイクロサービスドメインの集約の Mutation に関するイベントを発生させたときにリアルタイムの視覚的な更新を提供することによって、ユーザーインターフェース(UI)でのエンドユーザーの顧客体験を向上させる方法について説明します。

提案されたソリューションは、2 つのパターンを組み合わせたものです。1) Backends for Frontends (BFF) パターンでは、アプリケーションが 1 つの汎用 API バックエンドを持つ代わりに、ユーザー体験ごとに 1 つのバックエンドを持ちます。2) Publisher-Subscriber (pub/sub) パターンでは、マイクロサービスが送信側と受信側を結合せずに非同期に複数の関心を持つコンシューマにイベントを通知します。この 2 つのパターンを組み合わせると、フロントエンドクライアントは UI 対応のデータプロジェクションをロードし、イベント駆動の通知で UI をリフレッシュできるため、エンドユーザーにとって高性能なほぼリアルタイムの体験を実現できます。

RESTGraphQL の両方の API 開発者にこのソリューションを説明するために、それぞれの API テクノロジーに対応する 2 つの類似したアーキテクチャ図を提供します。

BFF パターン

Sam Newman 氏によると、Backend For Frontend (BFF) パターンとは、汎用的な API バックエンドを 1 つ持つのではなく、ユーザー体験ごとにバックエンドを 1 つ持つというものです。
従来、複数のタイプの UI に対応するためのアプローチは、単一のサーバーサイド API を提供し、新しいタイプのモバイルインタラクションをサポートするために、時と共に必要な機能を追加していくことでした。このアプローチでは、次のような課題が生じる可能性があります。

  1. モバイルデバイスは呼び出し回数が少なく、デスクトップとは異なる(おそらく少ない)データを表示したい。これは、API バックエンドがモバイルインターフェースをサポートするために追加機能が必要です。
  2. 最近のアプリケーションの UI は、エンドユーザーにリアルタイムのフィードバックを提供するために(例えばWebSocket 経由で)リアクティブな戦略を採用することが増えており、デバイスによってはそれをサポートするために異なるテクノロジースタックを実装することがあります。
  3. API バックエンドは、定義上、複数のユーザー向けアプリケーションに機能を提供しています。これは、新しいアーティファクトデプロイする際に、同じデプロイ可能なアーティファクトに多くの変更が加えられるため、単一の API バックエンドがボトルネックとなる可能性があります。

これらの課題に対処するために、Sam 氏は、ユーザー向けアプリケーションを、境界の外側にあるクライアント側アプリケーションと、境界の内側にあるサーバー側コンポーネント(BFF)の 2 つのコンポーネントで構成されると考えるべきだと提案しています。BFF は特定のユーザーエクスペリエンスと密接に連携しており、通常、ユーザーインターフェースと同じチームが保守します。そのため、UI の要求に応じて API を定義して適応させることが容易になり、クライアントとサーバー両方のコンポーネントのリリースを揃えるプロセスも単純化されます。
Sam 氏の提案に従い、BFF パターンは Netflix のような企業で採用されました。彼らの Android チームは Netflix Android アプリの API バックエンドをシームレスに交換し、エンドポイントの精査、可観測性、Netflix のマイクロサービス・エコシステムへの統合のレベルを上げて作業できるようにしました。

AWS 上のイベント駆動 BFF の構成要素

複数のマイクロサービスや単一のモノリスによって生成されたイベントを問わず、イベント駆動アーキテクチャを既に持っていると仮定すると、各エンドユーザー体験のための分離されたイベント駆動 Backends-for-Frontends(BFF)の構築を開始する準備ができています。

以下の図は、アーキテクチャとそのメッセージフローのハイレベルなビューを示します。右側がドメインパブリッシャーで、それぞれがドメイン固有の集約データベースを持ち、左側が BFF サブスクライバーで、それぞれがユーザーエクスペリエンス固有のプロジェクションデータベースを持つことを表しています。中央には、ドメインの状態変化を伝えるイベントバスがあり、パブリッシャーとサブスクライバーは切り離された状態を保つことができます。

このブログで紹介するイベント駆動 BFF ソリューションは、以下の共通コンポーネントに加え、テクノロジー固有の API に依存しています。

  • Change Data Capture (CDC) をサポートしているドメインプロジェクションテーブルを格納する NoSQL データベース。ここでは、Amazon DynamoDB を使用します。Amazon DynamoDB は、あらゆるスケールで 1 桁のミリ秒単位のパフォーマンスを実現する高速で柔軟な NoSQL データベースサービスです。
  • リクエストを処理し、CDC ストリームを API と統合するためのコンピュートレイヤー。ここでは、AWS Lambda を使用しています。サーバーレスでイベント駆動の計算サービスであり、サーバーやクラスタについて考えることなくコードを実行することができます。
  • API を保護するための認証と認可のメカニズム。ここでは、ユーザーのサインアップ、サインイン、アクセスコントロールのためのシンプルでセキュアなサービスである Amazon Cognito を使用します。

API Gateway を使ったイベント駆動 REST BFF の構築

Amazon API Gateway は、開発者があらゆる規模の API を簡単に作成、公開、維持、監視、および保護できるようにする、フルマネージドサービスです。API は、アプリケーションがバックエンドのサービスからデータ、ビジネスロジック、または機能にアクセスするための「フロントドア」として機能します。API Gateway を使用すると、リアルタイムの双方向通信アプリケーションを可能にする RESTful APIsWebSocket APIs を作成できます。

次のアーキテクチャ図は、ドメイン駆動設計(DDD)の概念を使用して、API Gateway WebSocket APIs を利用してエンドユーザー向けのイベント駆動 UI を作成する方法を説明したものです。この図の PDF 版は、このリンクを参照してください。

実装のステップ

  1. アプリケーションからのイベントを専用の BFF イベントコンシューマでキャッチします。これらは、フロントエンドで利用するために Amazon DynamoDB の非正規化されたデータプロジェクションを更新する役割を担います。
  2. UI ロード時に、フロントエンドクライアントは Amazon Cognito で認証し、Amazon API Gateway で構築された BFF API を呼び出してデータにクエリを実行します。データは、API Gateway で直接、または AWS Lambda で構築された BFF クエリハンドラ経由で、DynamoDB に取り込まれます。
  3. フロントエンドクライアントは、API Gateway が提供する BFF WebSocket エンドポイントに接続することで、その後のデータ変更をサブスクライブし、「接続中のクライアント」テーブルの更新をトリガーします。
  4. BFF イベントコンシューマを使用して、アプリケーションからすべての関連イベントを消費して処理し続けます。これらのコンシューマは、BFF データベース内の非正規化されたフロントエンドデータビューをリアルタイムで継続的に更新します。
  5. Amazon DynamoDB Streams を使用して BFF データベースのデータ変更に起因するすべてのイベントをサブスクライブし、新しいストリームレコードを検出したときにBFFストリームハンドラの Lambda 関数を非同期に呼び出すためにAWS Lambda でトリガーを登録します。
  6. BFF ストリームハンドラは、API Gateway の WebSocket に接続されたクライアントに通知をプッシュします。
  7. API Gateway からの変更通知をフロントエンドクライアントが受信すると、UI コンテンツを更新することができます。

AppSync を使用したイベント駆動 GraphQL BFF の構築

フロントエンド開発者が 1 つの GraphQL エンドポイントで複数のデータベース、マイクロサービス、および API に問い合わせることができるため、アプリケーションの開発を高速化できることから、組織が GraphQL による API を構築することを選択するケースが増えていることを私たちは見ています。

AWS AppSync は、Amazon DynamoDBAWS Lambda などのデータソースに安全に接続し、GraphQL API を簡単に開発できるようにするフルマネージドサービスです。一度デプロイされると、AWS AppSync は API リクエスト量に合わせて GraphQL API 実行エンジンを自動的にスケールアップおよびスケールダウンします。さらに、AppSync はパフォーマンスを向上させるためにキャッシュを追加し、オフラインのクライアントを同期させるクライアントサイドデータストアをサポートし、WebSocket 上の透過的サブスクリプションによるリアルタイムの更新をサポートします。

次のアーキテクチャ図は、ドメイン駆動設計(DDD)の概念を使用して、AppSync サブスクリプションを活用してエンドユーザー向けのイベント駆動 UI を作成する方法を説明するものです。この図の PDF 版は、このリンクを参照してください。

実施のステップ

  1. アプリケーションからのイベントを、目的に応じた BFF イベントコンシューマでキャッチします。これらは、フロントエンドで利用するために、 Amazon DynamoDB にデータの非正規化されたビューを保持する役割を担います。
  2. UI ロード時に、フロントエンドクライアントは Amazon Cognito認証し、AWS AppSync で構築された BFF API を呼び出して GraphQL でデータを照会します。その後、AWS AppSync が直接、または AWS Lambda で構築された BFF クエリハンドラを介して、DynamoDB にデータが取り込まみます。
  3. フロントエンドクライアントは、その後のデータ変更に対して、AWS AppSync のサブスクリプションを WebSocket 経由でサブスクライブします。
  4. BFF イベントコンシューマを使って、アプリケーションからのすべての関連イベントの消費と処理を継続します。これらのコンシューマーは、BFF データベース内の非正規化されたフロントエンドデータビューをリアルタイムで継続的に更新します。
  5. Amazon DynamoDB Streams を使用して BFF データベースのデータ変更に起因するすべてのイベントをサブスクライブし、新しいストリームレコードを検出したときに BFF ストリームハンドラの Lambda 関数を非同期に呼び出すようにAWS Lambdaにトリガーを登録します。
  6. BFF ストリームハンドラは次に、サブスクリプションを強制的にトリガーするために意図的に作成された AWS AppSync GraphQL スキーマの空の Mutation を呼び出し、クライアントに通知を送信します。
  7. AWS AppSync からの変更通知をフロントエンドクライアントが受信すると、UI コンテンツを更新することができます。

AWS AppSync は、リアルタイムデータ、接続、スケーラビリティ、ファンアウト、ブロードキャストなど、すべて AWS AppSync サービスによって処理されるシンプルな WebSockets を提供し、開発者はスケールでの WebSockets 接続の管理に必要な複雑なインフラに対処する代わりに、アプリケーションのユースケースと要件に集中することができます。

ステップ 6 の AWS Lambda ストリームハンドラ関数は、Amazon DynamoDB テーブルに新しいアイテムが挿入されたことをトリガーとして実行されます。これは、これらの項目を読み取り、AWS AppSync を更新して、新しいデータを警告します。このコードのサンプルは node.js で書かれており、マルチリージョンセットアップで AWS AppSync をデプロイするための AWS サンプルの一部として GitHub で公開されています。主な機能を抜粋して、以下に説明します。

以下のコードは、Lambda 関数のエントリポイントです。DynamoDB から、ストリームの新しいデータをトリガーとしたイベントを受信します。各新規エントリについて、それが(更新や削除ではなく)挿入されたデータである場合、データをパースし、executeMutation 関数を呼び出すことになります。

exports.handler = async(event) => {

  for (let record of event.Records) {
    switch (record.eventName) {
    
      case 'INSERT':
        // Grab the data we need from stream...
        let id = record.dynamodb.Keys.id.S;
        let name = record.dynamodb.NewImage.item.S;
        // ... and then execute the publish mutation
        await executeMutation(id, name);
        break;
        
      default:
        break;
    }
  }
  
  return { message: `Finished processing ${event.Records.length} records` }
}

以下のコードは、DynamoDB ストリームから受け取った新しいデータで AWS AppSync を Mutation させる executeMutation 関数です。サードパーティライブラリの Axios(node.js のプロミス型 HTTP クライアント)を使用して、AppSync の API エンドポイントに接続し、Mutation しています。

const executeMutation = async(id, name) => {

  const mutation = {
    query: print(publishItem),
    variables: {
      name: name,
      id: id,
    },
  };

  try {
  
    let response = await axios({
      url: process.env.AppSyncAPIEndpoint,
      method: 'post',
      headers: {
        'x-api-key': process.env.AppSyncAPIKey
      },
      data: JSON.stringify(mutation)
    });
    
  } catch (error) {
    throw error;
  }
};

Mutation は以下の GQL コードで生成されます。

const publishItem = gql`
  mutation PublishMutation($name: String!, $id: ID!) {
    publishItemsModel(input: {id: $id, item: $name}) {
      id
      item
    }
  }
`

まとめ

BFF パターンは、汎用的な API バックエンドを 1 つだけ持つのではなく、ユーザーエクスペリエンスごとに1つのバックエンドを持つことを指します。

BFF パターンをイベント駆動アーキテクチャで実装すると、マイクロサービスがドメイン集約の Mutation に関するイベントを発生させたときに、ほぼリアルタイムの視覚的な更新を提供することによって、UI 上のエンドユーザーの顧客体験を向上させることができます。

このブログ記事では、開発者が BFF パターンを RESTGraphQL API に適用して、UI 対応のデータプロジェクションをロードし、イベント駆動の通知で UI をリフレッシュする方法を説明しました。

Amazon API Gateway の詳細なリファレンスアーキテクチャはこちらから、AWS AppSync の詳細なリファレンスアーキテクチャはこちらから PDF でダウンロードすることができます。

本記事は、Backends for Frontends Pattern を翻訳したものです。

翻訳はソリューションアーキテクトの稲田(@inariku)が担当しました。