AWS Step Functionsを使ったNode.js APIの構築

このシリーズの最初の部分では、より大規模なソリューションの非同期コンポーネントを構築しました。今日は、リアルタイムで利用できるAPIインターフェースを構築します。

振り返り

これまでのところ、履歴書を非同期に処理するためのステップファンクションを2つ設定してきました。最初のステップファンクションは、S3バケットから履歴書を取り出し、ダウンロード可能なURLを生成します。2つ目のステップファンクションは、アップロードされた履歴書を解析してGitHubプロファイルを見つけ出すためにTextractを使用します。どちらの関数も、このデータをSQSキューで利用可能にするのに数秒かかります。

このパートでカバーする内容

以下の3つのエンドポイントでAPIを構築します。

  • バイナリ形式で履歴書をアップロードするためのPOSTエンドポイント。
  • SQSキューでの着信メッセージをチェックするためのGETエンドポイント。
  • 最後に、リクルーターが既に見たメッセージを削除するためのDELETEエンドポイントです。

このとき、既存のステップファンクションの上にAPIを構築します。APIは主に、ステートマシン、S3、SQSと対話します。

クライアント向けのAPIとバックグラウンドで実行されるステップファンクションは互いに独立しています。ステップファンクションは非同期で、ブロックすることなく実行できるため、一方のソリューションを変更してももう一方を変更する必要はありません。

これは、リアルな世界をより密接にモデル化しています。というのも、リクルーターは履歴書がアップロードされるのを待ちこがれて座っているわけではありません。彼らは準備が整い次第で履歴書の着信をチェックすることができます。

準備はいいですか?さあ、始めましょう!

履歴書API: Claudia.jsを使ってデプロイ

すでに2つのステップファンクションを含むメインのプロジェクトフォルダを持っているはずです。それに履歴書API用の新しいフォルダを追加してください。

node-aws-step-functions
`-- look-for-github-profile-step
`-- upload-resume-step
`-- resume-uploader-api

resume-uploader-apiの中でnpm initを実行し、package.jsonファイルを初期化してください。JSON内のnameプロパティがフォルダ名と一致するようにしてください。これにより、プロジェクトをデプロイしたときにLambda関数とGateway APIの名前が設定されます。

APIをデプロイするためにClaudia.jsを使用しているので、この依存関係をプロジェクトに追加してください。

> npm i claudia@latest --save-dev
> npm i claudia-api-builder@latest --save

package.jsonファイルを開き、scriptsセクションに以下のコマンドを指定してください。

"scripts": {
  "start": "claudia create --region us-east-1 --no-optional-dependencies --api-module pub/bundle --runtime nodejs18.x --memory 1536 --arch arm64",
  "deploy": "claudia update --no-optional-dependencies"
}

ここでは、Lambda関数に1.5GBの割り当てと、ARM Graviton2チップを指定しています。アプリがx86上で実行しなければならない特定のコードを持っていない限り、AWSは独自のARMチップを使用することを推奨しています。このメモリ割り当ては、クライアントアプリのレイテンシを低く保つのに役立ちます。アプリが使用するメモリ量について心配するのではなく、実際のユーザーにとってのCPUコアと知覚されるレイテンシに注目してください。

地域をよく確認して、最も近いものを選んでください。

S3、SQS、SFNの依存関係をプロジェクトに追加してください。

> npm i @aws-sdk/client-s3@latest @aws-sdk/client-sfn@latest @aws-sdk/client-sqs@latest --save

各依存関係について:

  • @aws-sdk/client-s3: アップロードされた履歴書が入るS3クライアント
  • @aws-sdk/client-sfn: 非同期プロセスをスタートさせるためのステップファンクションクライアント
  • @aws-sdk/client-sqs: メッセージを受け取り、パージするためのSQSクライアント

次に、web.jsファイルを作成し、以下の骨組みを追加してください。

const {
  PutObjectCommand, // アップロードされた履歴書をS3に置く
  S3Client,
} = require("@aws-sdk/client-s3");
const {
  SFNClient,
  StartExecutionCommand, // ステートマシンをスタートさせる
} = require("@aws-sdk/client-sfn");
const {
  SQSClient,
  ReceiveMessageCommand, // SQSメッセージを受け取る
  PurgeQueueCommand, // キュー内のSQSメッセージをパージする
} = require("@aws-sdk/client-sqs");
const ApiBuilder = require("claudia-api-builder");

const api = new ApiBuilder();

// 地域を確認する
const s3Client = new S3Client("us-east-1");
const sfnClient = new SFNClient("us-east-1");
const sqsClient = new SQSClient("us-east-1");

const s3BucketName = "<ユニークなバケット名>";
const stateMachineArn = "<ステートマシンのARN>";
const queueUrl = "<SQSキューのURL>";

// ここに残りのコードが入る

module.exports = api;

前の投稿から進んでいると仮定すると、すでにS3バケット、ステートマシン、SQSキューURLがあるはずです。これらの値はAWSコンソールで見つけることができます。地域が正しく設定されているかダブルチェックしてください。

ステートマシンのARNを見つけるには、AWSコンソールにログインし、「ステップファンクション」にアクセスしてください。そして、ステートマシンをクリックします。ARNはページの上部にあります。

SQSキューURLを見つけるには、「シンプルキューサービス」に移動し、キューをクリックします。URLもページの上部にあります。

APIにPOSTエンドポイントを追加

web.jsファイルに、以下のPOSTエンドポイントをAPIに追加してください。

api.post(
  "/",
  async function (request) {
    const storedFileName = request.queryString.fileName;
    const fileContents = request.body; // バイナリ形式 'application/pdf'

    const s3Command = new PutObjectCommand({
      Bucket: s3BucketName,
      Key: storedFileName,
      Body: fileContents,
    });

    await s3Client.send(s3Command);

    const sfnCommand = new StartExecutionCommand({
      input: JSON.stringify({ storedFileName }),
      stateMachineArn,
    });

    await sfnClient.send(sfnCommand);
  },
  {
    success: { code: 204 },
  }
);

これにより2つのことが行われます:

  • リクエストボディからの履歴書をS3バケットにアップロードする。
  • ステートマシンを介して非同期プロセスを開始する。

APIは即座に204 No Contentレスポンスを返します。これにより、APIはステートマシンの完了を待たずに知覚されるレイテンシが低くなります。ここで使用されるasync/awaitは、APIの論理的なブロックを行いません。

GETエンドポイント:メッセージのチェック

同じweb.jsファイルに以下を追加してください。

api.get("/", async function () {
  const command = new ReceiveMessageCommand({
    QueueUrl: queueUrl,
  });

  const response = await sqsClient.send(command);

  response.Messages =
    response.Messages?.map((message) => JSON.parse(message.Body)) || []; // 変換されたメッセージがなければ空のリスト

  return response;
});

処理済みの履歴書は最終的にSQSキューにたまります。ここでは、レスポンスオブジェクトのメッセージをチェックします。メッセージがまだなければ、空の配列を返します。

DELETEエンドポイント:古いメッセージの削除

最後に、web.jsファイルにこれを追加してください。

api.delete(
  "/",
  async function () {
    const command = new PurgeQueueCommand({
      QueueUrl: queueUrl,
    });

    await sqsClient.send(command);
  },
  {
    success: { code: 204 },
  }
);

パージコマンドは、SQSキューのメッセージをクリアします。これにより、リクルーターは着信した履歴書のレビューを終えた後、キューをクリアすることができます。

これらのAPIエンドポイントは、すべてバックグラウンドで起こる複雑な処理から完全に独立しており、高速な設計になっています。AWSにこれをデプロイする前に、より高速に実行されるようにバンドルサイズをトリムしましょう。

AWSにAPIをデプロイ

最適化されたバンドルを作成するには、webpackの依存関係をインストールしてください。

> npm i webpack@latest webpack-cli@latest --save-dev

resume-uploader-apiフォルダ内に、以下を含むwebpack.config.jsファイルを作成してください。

const path = require("path");

module.exports = {
  entry: [path.join(__dirname, "web.js")],
  output: {
    path: path.join(__dirname, "pub"),
    filename: "bundle.js",
    libraryTarget: "commonjs",
  },
  target: "node",
  mode: "production",
};

次に、最終バンドルのzipファイルをトリムするために.npmignoreファイルを作成してください。

node_modules/
claudia.json
event.json
webpack.config.js

最後に、package.jsonファイルを開いて、dependenciesプロパティをoptionalDependenciesに変更します。これにより、node_modulesフォルダのすべての依存関係が削除され、出力ファイルに含まれなくなります。

webpackが設置されたら、単にnpm startを実行してAWSにAPIをデプロイします。デプロイが成功裏に完了したら、JSON出力内のurlプロパティに注目してください。後でこれが必要になります。

APIのテスト

パーミッションをダブルチェックしてください。AWSコンソール内のIAMにあるresume-uploader-api-executorロールを見つけ、このロールにAmazonS3FullAccessAmazonSQSFullAccessAWSStepFunctionsFullAccessのパーミッションを追加します。

AWS Gatewayはすでにバイナリ形式を処理しているため、application/pdfとしてコンテントタイプを設定する限り、CURLを使用して履歴書を簡単にアップロードすることができます。gatewayとデプロイツールがこれを自動的に処理します。

前の投稿で使用した ExampleResume.pdf を見つけます。自分で作成した場合は、それを使用してください。次にCURLで履歴書をアップロードします。

> curl -i -X POST -H "Content-Type: application/pdf" --data-binary "@ExampleResume.pdf" https://<GATEWAY-API-ID>.execute-api.<REGION>.amazonaws.com/latest?fileName=ExampleResume.pdf

これは即座に204のHTTPステータスコードで応答するはずです。次に、SQSキューの状態をチェックするために別のリクエストを発行します。

> curl -i -X GET -H "Accept: application/json" https://<GATEWAY-API-ID>.execute-api.<REGION>.amazonaws.com/latest

これは、あなたがどれほど速くタイプするかによって、空の配列を返すはずです。もし数秒以上かかる場合、APIは処理済みの履歴書とともに応答するでしょう。実際の処理はバックグラウンドで行われています。我々のAPIはリアルタイムで消費するためのものであり、いつでも戻ってキューの状態を確認することができます。

履歴書のレビューを終えたら、キューをクリアすることができます。

> curl -i -X DELETE -H "Accept: application/json" https://<GATEWAY-API-ID>.execute-api.<REGION>.amazonaws.com/latest

なんらかの理由でGATEWAY-API-IDREGIONを含むURLを失った場合、AWSコンソールにログインし、API Gatewayにアクセスしてresume-uploader-apiをクリックします。Invoke URLは「ステージ」(最新をクリック)の下にあります。

まとめ

このシリーズの第一部では、Claudia.jsを使用してAWSにLambdaステップファンクションをデプロイしました。その後

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/appsignal/aws-step-functions-with-nodejs-build-an-api-3fn7