Amazon Web Services ブログ

AWS AppSync GraphQL API の JavaScript リゾルバをはじめよう

AWS AppSync は、アプリケーションをデータに接続するスケーラブルな API を簡単に構築するためのマネージドサービスです。開発者は AppSync を使用して、Amazon DynamoDBAWS Lambda、HTTP API などのデータソースと相互作用するGraphQL API を構築しています。例えば、Amazon DynamoDB のテーブルに接続し、AppSync リゾルバに直接データアクセスロジックを実装することができます。

これまで、開発者は AppSync のビジネスロジックを実装するためには Velocity Template Language (VTL) しか使えませんでした 。VTL は強力ですが、テンプレート言語に精通していない多くの開発者にとって困難なものでした。今回、AppSync パイプラインリゾルバーコードと AppSync ファンクションコードを JavaScript で記述できるようになりました。この記事では、この機能の概要を説明し、JavaScript リゾルバを使い始める方法を紹介します。

おさらい

実際に構築を始める前に AppSync のコアとなるコンセプトをおさらいしておきましょう。AppSync では、データソース、リゾルバ、関数を定義することができます。リゾルバは AppSync に GraphQL リクエストの処理方法を指示し、レスポンスを GraphQL タイプにマップバックします。AppSync のリゾルバは、パイプラインで最大10個の関数を設定でき、それぞれがデータソースに接続されます。リゾルバが呼び出されると、各関数が順番に実行されます。

今回のアップデートによって、JavaScript を使ってパイプラインのリゾルバと AppSync の関数を書くことができます。リゾルバや関数が呼び出されると、APPSYNC_JS ランタイムで実行されます。これは、AppSync サービス内に存在するカスタムビルドの JavaScript ランタイムです。デプロイするための追加のインフラはなく、あなたの関数はサービスの使用状況に応じて拡張されます。AppSync の JavaScript 関数は、以下のようなユースケースに最適です。

  • DynamoDB、Amazon OpenSearch Service、AWS Lambda、Amazon Aurora Serverless との連携。
  • HTTP API との連携と受信ヘッダの受け渡し。
  • HTTP データソースを使用したAWSサービスとの連携。(AWS AppSync が提供されたデータソースロールで自動的にリクエストに署名する)。
  • データソースにアクセスする前のアクセス制御を実装する。
  • リクエストの実行前に、取得したデータのフィルタリングを実装する。
  • クエリとミューテーションにおけるキャッシングとサブスクリプション接続を制御する。

どのように動くのか

JavaScript を使ってリゾルバや関数を定義するには、リクエストハンドラとレスポンスハンドラという 2 つの関数をエクスポートするコードファイルを提供します。リクエストハンドラはコンテキストオブジェクトを引数に取り、データソースの呼び出しに使用する JavaScript オブジェクトの形でリクエストペイロードを返します。サポートされているリクエスト・オブジェクトの詳細は、Resolver のリファレンス(JavaScript)のドキュメントに記載されています。レスポンスハンドラは、実行されたリクエストの結果を含むペイロードをデータソースから返します。レスポンスハンドラは、このペイロードを適切な形式に変換して返します。
以下の例では、リクエストハンドラが DynamoDB データソースからアイテムを取得するコードです。レスポンスハンドラでは、category から __typename を抽出し、メッセージを記録し、型とともに元の結果を返します。

import { util } from '@aws-appsync/utils';
/**
 * Request a single item with `id` from the attached DynamoDB table datasource
 * @param ctx contextual information about the request
 */
export function request(ctx) {
  const { args: { id } } = ctx
  return { 
    operation: 'GetItem',
    key: util.dynamodb.toMapValues(id)
  }
}

/**
 * Returns the result directly
 * @param ctx contextual information about the response
 */
 function response(ctx) {
  const { result } = ctx;
  // category has format: Type#subtype#
  const typename = result.category.split('#')[0];
  console.log(`result ${result.id} -> type: ${typename}`);
  return {
    ...result,
    __typename: typename,
  };
}

パイプラインリゾルバも同じように定義します。パイプラインリゾルバは、パイプラインの関数の実行前と後のロジックを持ちます。そのリクエストハンドラは最初の関数のリクエストの前に実行され、 そのレスポンスハンドラは最後の関数のレスポンスの後に実行されます。リゾルバの 前のロジックは、パイプラインの関数が使用するデータを設定することができます。リゾルバの後のロジックは、GraphQL フィールド出力タイプにマッピングされたデータを返す役割を担います。以下は、リクエストハンドラ内に実行ロジックを持たず、レスポンスハンドラ内に最後の関数の結果を返すリゾルバコードです。このリゾルバーコードは、事前または事後のデータ処理が必要ないユースケースに適してます。

export function request(ctx) {
  return {}
}

export function response(ctx) {
  return ctx.prev.result
}

@aws-appsync/utils パッケージからユーティリティをインポートして使用することができます。このパッケージは、データやデータソースとの対話を容易にするユーティリティを提供する utilextensionsモジュールをエクスポートします。例えば、DynamoDB のリクエストを作成する際には util.dynamodb.toMapValues ユーティリティがよく使われます。このユーティリティに値のオブジェクトを渡すと、それを適切な DynamoDB 形式の値のオブジェクトに変換します。util.autoId() のような ID 生成ユーティリティを使えば、128 ビットのランダムな UUID を生成することができます。VScode のようなコード IDE 内では、パッケージが自動補完し、利用可能なすべてのユーティリティのドキュメントを提供します。
リゾルバや関数を定義する方法としては、AWS CLI、AWS Cloudformation、AWS CDK、AppSync コンソールがあります。例えば、新しい JavaScript パイプラインリゾルバを作成するには、ランタイムと、使用するランタイムのバージョンを指定する必要があります。現在、利用可能なランタイムは APPSYNC_JS のみで、ランタイムのバージョンは1.0.0 です。

$ aws appsync create-function --api-id "<api-id>" \
  --name "<function-name>" \
  --code "file://<file-location>" \
  --data-source-name "<data-source-name>" \
  --runtime name=APPSYNC_JS,runtimeVersion=1.0.0

どのような種類の機能がサポートされていますか?

APPSYNC_JS ランタイムは、ECMAScript (ES) version 6.0 と同様の機能を提供し、その機能のサブセットをサポートします。機能の完全なリストは、リゾルバのリファレンス・ドキュメントに記載されています。APPSYNC_JS ランタイムは、AppSync コンテキスト内で実行するように設計されているため、サポートされていない特定の JavaScript の機能が存在します。例えば、ネットワークとファイルアクセスは不可能で、async/await や promise は利用できません。IDE でのコーディング体験をサポートするために、コードに含まれるサポートされていない機能を警告する新しい eslint プラグイン @aws-appsync/eslint-plugin を使用することができます。実際のプロジェクトで linting ルールを使用するには、パッケージのインストールから始めます。

npm install @aws-appsync/eslint-plugin

実際のプロジェクトで eslint を使用する場合は以下を実行してください。

npm init @eslint/config

生成された eslint 設定ファイルにおいて、extends プロパティを追加または更新します。

{
  "extends": ["plugin:@aws-appsync/base"]
}

リゾルバや関数の JavaScript コードを保存する際に、AppSync サービスもコードの検証を行うため、悪いコードやサポートされていない機能を使ったコードによるランタイムエラーが発生しないようにします。

最初の JavaScriptリゾルバを作成する

最近、Serverless Land で、Amazon SNS トピックにメッセージをパブリッシュする AppSync Graph QL API の構築手法を公開しました。この手法を使って、GraphQL API をセットアップし、リゾルバを VTL から JavaScript に変更する手順を説明します。CDK スタックは AppSync GraphQL API を定義し、HTTP データソースとして SNS をセットアップします。スキーマは以下のとおりです。

type Query {
    publish(from: String, message: String): Response
}

type Response {
    MessageId: String!
    SequenceNumber: String
}

HTTP データソースを使用すると、AppSync から AWS サービスの API と簡単に通信することができます。データソースと共にAppSyncがデータソースへアクセスすることを許可する IAM ロールを設定します。あなたがデータソースにアクセスするために GraphQL クエリまたはミューテーションを使用するようにすると、AppSync は提供されたロールを使用してリクエストに署名します。

まず始めに、ここに記載されている手順に従ってスタックをデプロイします。手順は以下の通りです。

git clone https://github.com/aws-samples/serverless-patterns/
cd serverless-patterns/cdk-appsync-sns
npm install
npm run build
npm run cdk deploy

これは ToSnSApi という AppSync GraphQL API をデプロイするものです。デプロイが完了したら、デプロイしたリージョンの AWS AppSync コンソールを開き、API リストから ToSnSApi を選択します。左側のメニューで、Schema を選択します。スキーマエディターで、Publish フィールドの横にある sns をクリックします。

リゾルバ画面で、SNS のトピック ARN をメモしてコピーします。この ARN は JavaScript リゾルバで使用します。パイプラインリゾルバに変換するをクリックしConvert を選択して確定します。これで、リゾルバが JavaScript パイプラインリゾルバに変換され、現在の VTL コードが新しい関数に保存されます。

では、下記のサンプルでリゾルバのコードを更新し、 を先程の ARN に置き換えてください。そして、save を選択します。ここでは、パイプラインリゾルバを使って TOPIC_ARN を stash に保存しています。このように、関数内で値をハードコードすることはありません。スキーマに別の SNS トピックにメッセージを送信する publish メソッドを追加した場合、この関数を再利用して、特定のトピックの ARN を stash 経由で渡すことができます。

export function request(ctx) {
    ctx.stash.TOPIC_ARN = '<your-topic-arn>'
    return {};
}

export function response(ctx) {
    return ctx.prev.result;
}

下の Functions セクションで、関数名をクリックすると、関数定義に移動します。
Function 画面で、右上のメニューから Actions を選び、Update Runtime を選び、APPSYNC_JS ランタイムオプションを選択します。これにより、リゾルバの構成が VTL から APPSYNC_JS に変更されます。

以下のサンプルでコードを更新し、Save を選択します。このコードでは、リクエストハンドラは、スタッシュからTOPIC_ARN を、引数からメッセージを取得します。そして、publishToSNSRequest 関数を呼び出して、有効な SNS パブリッシュリクエストを返します。レスポンスハンドラでは、ステータスコードをチェックします。値が 200 の場合、リクエストは成功し、ボディから結果を抽出します。そうでない場合は、GraphQL レスポンスにエラーを追加します。util.xml.toMap ユーティリティを使用して、XML レスポンスを JavaScript オブジェクトに変換しています。

import { util } from '@aws-appsync/utils';

export function request(ctx) {
  const { TOPIC_ARN } = ctx.stash;
  const { message } = ctx.arguments;
  return publishToSNSRequest(TOPIC_ARN, message);
}

export function response(ctx) {
  const result = ctx.result;
  if (result.statusCode === 200) {
    // if response is 200
    // Because the response is of type XML, we are going to convert
    // the result body as a map and only get the User object.
    const body = util.xml.toMap(result.body)
    console.log('respone body -->', body)
    return body.PublishResponse.PublishResult;
  }
  // if response is not 200, append the response to error block.
  util.appendError(result.body, `${result.statusCode}`);
}

function publishToSNSRequest(topicArn, values) {
  const arn = util.urlEncode(topicArn);
  const message = util.urlEncode(JSON.stringify(values));
  const parts = [
    'Action=Publish',
    'Version=2010-03-31',
    `TopicArn=${arn}`,
    `Message=${message}`,
  ];
  const body = parts.join('&');
  return {
    method: 'POST',
    resourcePath: '/',
    params: {
      body,
      headers: {
        'content-type': 'application/x-www-form-urlencoded',
      },
    },
  };
}

左サイドメニューの Queries をクリックし、このクエリーを送信すると使用できます。

query MyQuery {
  publish(from: "brice", message: "hello world") {
    MessageId
    SequenceNumber
  }
}

SNS のトピックとそのサブスクライバーにメッセージを送信します。以下のような応答があります。

API でログを有効にすると、Amazon CloudWatch Logs でログステートメントが行われた場所のログを見つけることができます。

VTL からの移行

VTL から移行する場合、いくつかの方法で JavaScript を使い始めることができます。パイプラインリゾルバは VTL または JavaScript で書かれた関数を使用できるので、 既存のパイプラインリゾルバに JavaScript の関数を追加し始めることができます。これは、時間をかけて段階的に関数を移行したい場合に役立ちます。VTL ユニットリゾルバを使用している場合、それをVTL 関数に変換し、JavaScript パイプラインリゾルバに追加することができます。そして、VTL 関数を JavaScript 関数に置き換える前に、リゾルバ全体の挙動を検証することができます。AWS コンソールでリゾルバをトランスフォームすることができます(上記参照)。有効な VTL テンプレートは、JSON エンコードされた文字列として評価されることをご認識ください。関数リクエストハンドラでリクエストを構築したり、レスポンスハンドラで結果を返したりする際には、文字列を返してはいけません。その代わりに、AppSync に行わせたいリクエストを定義したオブジェクトを直接返すか、特定のレスポンスに必要なデータを返します。

まとめ

JavaScript リゾルバの導入により、開発者は AppSync のビジネスロジックを書くために、プログラミング言語を利用できるようになりました。この機能は現在、AppSync が サポートされているすべてのリージョンで利用可能です。まずは、ドキュメント、DynamoDB チュートリアル、aws-appsync-resolver-samples リポジトリをご覧ください。

この記事は、Getting started with JavaScript resolvers in AWS AppSync GraphQL APIs を翻訳したものです。翻訳はソリューションアーキテクトの稲田大陸が担当しました。