Amazon Web Services ブログ
AWS AppSync と RDS Data API を使って Amazon Aurora MySQL データベース用の GraphQL API を構築する方法
AWS AppSync が、Data API で構成された Amazon Auroraクラスタ上で稼働している既存の MySQL や PostgreSQL データベースのテーブルに基づいて、GraphQL API を簡単に作成できるようになりました。既存のデータベース用の API を構築する場合、開発者は通常、テーブルを正確に表現するインターフェースを構築しなければなりませんが、これには時間がかかり、エラーが発生しやすいプロセスです。AppSync は、データベースを検出し、それに一致する GraphQL 型を生成できる新しいイントロスペクション機能によってこの問題を解決します。AppSync コンソールでは、この新機能を使用して、コードを記述することなく、わずか数ステップでデータベースからすぐに使用できる GraphQL API を生成できます。さらに、Amazon Relational Database Service (RDS) 用の JavaScript リゾルバにも改良が加えられており、新しい SQL タグ付きテンプレートと SQL ヘルパー関数により、リゾルバで SQL ステートメントを簡単に記述できるようになっています。
本記事では、API を即座に構築するために AWS コンソールでこの機能を使い始める方法を紹介し、JavaScript リゾルバで新しい RDS ユーティリティライブラリを使用する方法を紹介します。
注:このブログの機能は、RDS Data API をサポートする Amazon RDSデータベースを使用しています。Data API をサポートしていない MySQLや PostgreSQL データベースに接続するには、「既存の MySQL と PostgreSQL データベース用の GraphQL API の作成」を参照してください。
AppSync コンソールでの設定
AppSync の新しいイントロスペクション機能は、Data API で構成された Amazon Aurora クラスターで実行されている既存のデータベースで使用できます。例えば、以下のテーブルが定義された MySQL データベースがあり、API を提供したいとします。
CREATE TABLE conversations (
id INT NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE messages (
id VARCHAR(36) PRIMARY KEY,
conversation_id INT NOT NULL,
sub VARCHAR(36) NOT NULL,
body TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (conversation_id) REFERENCES conversations(id)
);
CREATE TABLE conversation_participants (
conversation_id INT NOT NULL,
sub varchar(36) NOT NULL,
last_read_at DATETIME,
PRIMARY KEY (conversation_id, sub),
FOREIGN KEY (conversation_id) REFERENCES conversations(id)
);
AWS AppSync コンソールで、Create API をクリックし、GraphQL API で Start with an Amazon Aurora cluster を選択します。Next をクリックし、API に名前を付け、次の画面でデータベース情報を入力してイントロスペクションを開始します。
Aurora クラスタに Data API を設定し、データベースユーザーの認証情報を AWS Secrets Manager に保存しておく必要があります。このユーザーには、データベース、スキーマ、テーブルの設定を読み取る権限が必要です。設定の詳細については、Data API ドキュメントを参照してください。Secrets Manager のシークレットに対して GetSecretValue、クラスタに対して ExecuteStatement を実行する権限が必要です。また、自分のリソースでアクセスするために AppSync の権限を付与する必要があります。コンソールでロールを作成することもできますし、自分で用意することもできます。Import をクリックして、イントロスペクション処理を開始します。完了すると、検出されたテーブルは以下のように表示されます。
デフォルトでは、型名はテーブル名と同じですが、カスタマイズすることもできます。生成されるスキーマからテーブルを除外することもできます。以下のように型名を変更してください。
Conversation_Participants
→Participant
Conversations
→Conversation
Messages
→Message
Next をクリックし、Create queries, mutations, and subscriptions for all models を選択します。Next をクリックし、変更内容を確認して、Create API をクリックします。スキーマの作成を開始し、リゾルバをフィールドにアタッチします。これで、クエリエディタからデータベースとやり取りしたり、GraphQL API に接続するクライアントアプリケーションを構築したりできるようになります。API を使用するには、クエリエディタに向かいます。左側のメニューから Queries を選択します。まずは新しい会話 (Conversation) を作成しましょう。
mutation CreateConvo {
createConversation(input: {id: 1, name: "stand up meeting"}) {
created_at
id
name
}
}
これでデータベースに会話が追加されます。次にメッセージを追加しましょう。
mutation CreateMsg {
createMessage(input: {body: "Hello world! Things are looking good.", conversation_id: 1, id: "new-message", sub: "johndoe"}) {
body
conversation_id
created_at
id
sub
}
}
AppSync のリアルタイム機能はすぐに使用できます。例えば、会話で新しいメッセージを聞くには、新しいタブまたはウィンドウでクエリエディタを開き、フォローサブスクリプションを入力します。
subscription OnCreate {
onCreateMessage(conversation_id: 1) {
body
conversation_id
created_at
id
sub
}
}
元の Queries タブで、別のメッセージを送信します。
mutation CreateMsg {
createMessage(input: {body: "Hello again. Nothing to report", conversation_id: 1, id: "2nd-message", sub: "johndoe"}) {
body
conversation_id
created_at
id
sub
}
}
2 つめのクエリエディタにサブスクリプションがトリガーされたことが表示されます。
自動生成されたリゾルバを編集し、必要に応じてカスタマイズすることができます。例えば、新しいメッセージが作成されるたびに API が ID を自動生成するように変更してみます。createMessage
リゾルバを以下のように更新します。
import { util } from '@aws-appsync/utils';
import { insert, select, createMySQLStatement, toJsonObject } from '@aws-appsync/utils/rds';
export function request(ctx) {
const { input: values } = ctx.args;
values.id = util.autoUlid() // <<< set the ULID
const doInsert = insert({ table: 'messages', values });
const doSelect = select({
table: 'messages',
columns: '*',
where: { id: { eq: values.id } },
limit: 1,
});
return createMySQLStatement(doInsert, doSelect);
}
export function response(ctx) {
const { error, result } = ctx;
if (error) {
return util.appendError(error.message, error.type, result);
}
return toJsonObject(result)[1][0];
}
上のコードでは、2 つのリクエストをデータベースに送っています。1 つめは、指定された ULID (Universally Unique Lexicographically Sortable Identifier) で新しいメッセージを作成します。2 つめは挿入された行をフェッチしてデータベースからデータを返します。これは、MySQL を使用して作成されたばかりの行 (自動生成されたカラムと値を含む) を取得するときに便利なパターンです。レスポンスから 2つめのオブジェクト (インデックス1) を取得します。これは、私が送信した 2 つめのステートメント (doSelect
) の結果に対応します。
次に、スキーマの CreateMessageInput
Input を更新して、id
フィールドを削除します。
input CreateMessageInput {
# id: String! # << comment out or remove
conversation_id: Int!
sub: String!
body: String!
created_at: String
}
新しいメッセージを送信します。
query ListMsgs {
listMessages(filter: {conversation_id: {eq: 1}, created_at: {ge: "2023-11-13", lt: "2023-11-13 22:23"}, sub: {ne: "john"}}) {
items {
id
created_at
body
sub
}
}
}
生成されたIDでレスポンスが返ってきます。
{
"data": {
"createMessage": {
"body": "up and up",
"conversation_id": 1,
"created_at": "2023-11-13 23:24:06",
"id": "01HF5FZNM3M9PEYC1234567890",
"sub": "john"
}
}
}
いくつかのメッセージを追加したところで、会話メッセージをすべて選択してみましょう。例えば、ここでは created_at
タイムスタンプと sub
値でフィルタリングしています。
query ListMsgs {
listMessages(filter: {conversation_id: {eq: 1}, created_at: {ge: "2023-11-13", lt: "2023-11-13 22:23"}, sub: {ne: "john"}}) {
items {
id
created_at
body
sub
}
}
}
RDS の新しいユーティリティ関数の使用
RDS の新しいユーティリティを使用して、データベーステーブルを操作することができます。Conversation
型を変更して、participants
フィールドを追加してみましょう。このフィールドは、最近読まれたメッセージ (last_read_at
) に基づいて、最近アクティブになった会話参加者の ID を返します。
type Conversation {
id: Int!
name: String!
created_at: String
participants: [String]
}
次に、@aws-appsync/utils/rds
が提供する select
ユーティリティ関数を使ってカスタム select 文を書くために、getConversation
リゾルバを更新します。
import { util } from '@aws-appsync/utils';
import {
select,
createMySQLStatement,
toJsonObject,
} from '@aws-appsync/utils/rds';
export function request(ctx) {
const { id } = ctx.args;
const doSelect = select({
table: 'conversations',
columns: '*',
where: { id: { eq: id } };,
limit: 1,
});
const doGetLatest = select({
table: 'conversation_participants',
columns: ['sub'],
where: { conversation_id: { eq: id } },
orderBy: [{ column: 'last_read_at', dir: 'DESC' }],
limit: 10,
});
return createMySQLStatement(doSelect, doGetLatest);
}
export function response(ctx) {
const { error, result } = ctx;
if (error) {
return util.appendError(error.message, error.type, result);
}
const res = toJsonObject(result);
const convo = res[0][0];
if (convo) {
convo.participants = (res[1] ?? []).map((p) => p.sub);
}
return convo;
}
リゾルバでは、最大 2 つのステートメントをデータベースに送信することができるので、1 回の実行で会話 (Conversation) とその参加者を取得することができます。createMySQLStatement
関数は、MySQL ステートメントを適切にエスケープし、引用符で囲むリクエストを作成します。変更してみましょう。クエリエディタでクエリを実行します。
query get {
getConversation(id: 1) {
id
participants
}
}
以下の結果が返ってきます。
{
"data": {
"getConversation": {
"id": 1,
"participants": [
"John",
"Sarah"
]
}
}
}
カスタム SQL 文の作成
新しい SQL タグ付きテンプレートを使って独自の SQL 文を書くことができます。タグ付きテンプレートを使うと、テンプレート式を通して実行時に動的な値を受け入れる静的な SQL 文を書くことができます。会話要約クエリをスキーマに追加し、新しい Summary
型を追加してみましょう。
type Query {
getConversationSummary(id: Int!, since: AWSDate!): Summary
}
type Summary {
id: Int!
total_messages: Int
total_words: Int
total_participants: Int
}
次に、getConversationSummary
にリゾルバをアタッチします。
import {
sql,
createMySQLStatement,
toJsonObject,
typeHint,
} from '@aws-appsync/utils/rds';
export function request(ctx) {
const query = sql`
SELECT
c.id AS id,
COUNT(DISTINCT m.id) AS total_messages,
COUNT(DISTINCT cp.sub) AS total_participants,
SUM(LENGTH(m.body) - LENGTH(REPLACE(m.body, ' ', '')) + 1) AS total_words
FROM
conversations c
LEFT JOIN
messages m ON c.id = m.conversation_id
LEFT JOIN
conversation_participants cp ON c.id = cp.conversation_id
WHERE
c.id = ${ctx.args.id}
AND m.created_at >= ${typeHint.DATE(ctx.args.since)}
GROUP BY
c.id, c.name;
`;
return createMySQLStatement(query);
}
export function response(ctx) {
return toJsonObject(ctx.result)[0][0];
}
ここでは、sql
タグ付きテンプレートを使って SQL 文を書いています。SQL タグ付きテンプレートを使うと、式によって動的な値のみを受け付ける静的なステートメントを書くことができます。式を通して渡された値は、プレースホルダを通して自動的にデータベースエンジンに送られます。また、since
引数がデータベースエンジンによって DATE
型として扱われることを示すために、型ヒントを使用しています。
変更を保存し、クエリを実行します。
query get {
getConversationSummary(id: 1, since: "2023-01-01") {
id
total_messages
total_participants
total_words
}
}
以下の結果が返ってきます。
{
"data": {
"getConversationSummary": {
"id": 1,
"total_messages": 9,
"total_participants": 2,
"total_words": 66
}
}
}
まとめ
Aurora クラスターで稼働している既存の MySQL データベースから AppSync GraphQL API を作成する手順を紹介しました。この記事では MySQL に焦点を当てましたが、PostgreSQL データベースでも同じことができます。ご自身のデータベースで始めるには、AppSync ドキュメントで Data API による RDS イントロスペクションの詳細をご覧ください。RDS 用の JavaScript リゾルバの新しいユーティリティの詳細については、JavaScript リゾルバのリファレンスを参照してください。ガイド付きの導入については、Aurora PostgreSQL with Data API のチュートリアルを参照してください。独自の JavaScript リゾルバを書き始めるには、@aws-appsync/utils パッケージの最新版をダウンロードまたは更新してください。
本記事は「Build a GraphQL API for your Amazon Aurora MySQL database using AWS AppSync and the RDS Data API」を翻訳したものです。