はじめに
昨年、ANGEL Dojo 2024に参加し、3か月間でサービスの企画から開発までを行う経験をしました。
私たちのチームは、電話練習アプリ「Tele Talk Tutor(T3)」を開発しました。
このサービスは、電話対応が苦手な方向けに、営業アポ取りや飲食店アルバイトの予約対応など、リアルな場面での電話練習を提供します。
この電話練習機能にて、AppSyncからLambdaを呼び出す実装を行ったので、その流れを本記事に残しておこうと思います。
関連記事
本記事では取り上げていませんが、ユーザの電話対応内容からフィードバックを提供する機能も開発しました。以下の記事でチームメンバーが一部紹介してくれています!
電話練習機能では、ユーザの音声をWebSpeechAPIでテキストに起こし、Lambdaに送信してBedrockで返答内容を生成して返却します。
ここで、フロントエンドからLambdaを呼び出すため、AppSyncを設計しています。
AppSyncとは
AWS AppSyncは、GraphQLを使用してDynamoDBやLambdaなどのデータソースに対してアクセスや操作ができるサービスです。
AppSyncの設計(GraphQL)
API作成
まずは、AWS AppSyncコンソールの画面から、GraphQL APIを作成します。
※リソースは独自で設定するよう選択するので、詳細設定は省略
スキーマ
次に、左メニューからスキーマ設定画面を開いて、パラメータやデータ操作の定義を行っていきます。
Mutation要求の引数で使用する項目をinput
型で定義します。(!マーク:必須項目)
戻り値はtype
を使用して定義します。
input SendDataInput {
sessionId: String!
message: String!
}
type SendDataOutput {
audioUrl: String!
}
データ操作の定義については、今回の処理は単純なデータ取得(Query
)ではなく、リクエストデータからのレスポンスデータ作成なので、Mutation
を使用しています。
type Mutation {
sendData(input: SendDataInput!): SendDataOutput
}
これでスキーマ定義は完了です。
データソース
次に、左メニューからデータソース設定画面に行き、以下項目を入力します。
ここまで選択すると、以下の項目が表示されるため、それぞれ入力します。
リゾルバー
再度スキーマ画面に戻り、画面右のリゾルバーから、先ほど定義したMutationのアタッチを選択します。(以下画像では、既にアタッチ済みのためボタンは表示されていませんが、設定が完了すると画像の通りになります。)
- リゾルバータイプ
今回は単一のデータソース(Lambda)からの取得になるので、「ユニットリゾルバー」を選択します。 - その他設定(リゾルバ―ランタイム)
「Velocityテンプレート言語(VTL)」を選択します。 - データソース
先ほど設定したものを選択します。
作成が完了すると、次の設定画面へ移ります。
リクエストマッピングテンプレート
ここでは、サンプルテンプレートから「invoke and forward arguments」を選択します。
以下の内容が自動で設定されます。
#**
The value of 'payload' after the template has been evaluated
will be passed as the event to AWS Lambda.
*#
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": $util.toJson($context.args)
}
レスポンスマッピングテンプレート
同じくサンプルテンプレートから「Return lambda result」を選択します。
こちらも、以下の内容が自動で設定されます。
$util.toJson($context.result)
これで、AppSync側の設定は完了です!
TypeScriptでの実装
今回フロントエンドはTypeScriptで実装していましたので、AppSyncとの接続方法も残しておきます。
一部気になる設計もあるかと思いますが、とりあえず動くことがメインの記述となります。
開発環境
- Node.js:v20.14.0
- TypeScript:v5.5.3
// Apollo ClientがGraphQLリクエストを送信するためのAppSyncエンドポイントを設定
const httpLink = new HttpLink({
uri: "https://xxx",
});
// AppSyncエンドポイントへのアクセスを認証するためのAPIキーを設定
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
"x-api-key": "yyy",
},
};
});
// Apollo Client(状態管理ライブラリ)が使用するリンクを作成
const link = ApolloLink.from([authLink, httpLink]);
// ApolloClientのインスタンスを作成
const client = new ApolloClient({
link: link,
cache: new InMemoryCache(),
});
// GraphQLミューテーションを定義
const SEND_DATA_MUTATION = gql`
mutation SendData($input: SendDataInput!) {
sendData(input: $input) {
audioUrl
}
}
`;
// インターフェースを定義
interface SendDataOutput {
audioUrl: string;
}
interface SendDataInput {
sessionId: string;
message: string;
}
// Lambdaに送信する関数を定義
async function sendDataToLambda(data: SendDataInput) {
const response = await client.mutate<{ sendData: SendDataOutput }>({
mutation: SEND_DATA_MUTATION,
variables: { input: data },
});
// 音声ファイルのURLを取得
const audioUrl = response.data?.sendData.audioUrl;
// その他処理
}
// 使用例
const iconClick = () => {
const data = {
sessionId: "sessionId",
message: "message",
};
sendDataToLambda(data);
};
あとがき
私は、普段の業務でAWSやTypeScriptをほとんど使わない初心者でしたが、そこまで複雑な設計ではなかったので、使いながら慣れることができました。
本機能だけでなく、アプリケーション内の別機能でもAppSyncを利用しましたが、異なるDBやLambda関数などを1つのGraphQLスキーマで扱うので単一のエンドポイントからデータを取得できて、開発の負担はかなり減ったと思います。