11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS AmplifyとAWS×フロントエンド #AWSAmplifyJPAdvent Calendar 2023

Day 22

AWS AppSync がアツい! RDS Data API を使って GraphQL API を爆速で作ってみた

Last updated at Posted at 2023-12-22

本記事の続編を出しました。ぜひこちらの記事からお読みください。

こんにちは。いなりくです✋
本記事は、AWS Amplify と AWS × フロントエンド #AWSAmplifyJP Advent Calendar 2023 の 22 日目の記事です。

本記事は 2023 年 12 月 23 日時点の情報に基づいて執筆しています。

2023 年 11 月 27 日に、AWS AppSync は RDS Data API を使用して Amazon Aurora クラスター内のデータベースに対してイントロスペクションを行うことで、検出したテーブルに適合した GraphQL API のインターフェイスを簡単に作成することが可能になりました。

RDS Data API を使用して設定された Amazon Aurora クラスターに対する AWS AppSync のサポートが改善

すごい便利な機能ですが、RDS Data API が 2023 年 11 月 27 日時点では Aurora Serverless v1 のみの対応だったため利用できるケースが限定的でした。

しかし、ちょうど昨日 (2023 年 12 月 21 日)、Amazon Aurora PostgreSQL の Serverless v2 と Provisoned で RDS Data API が対応したため、AWS AppSync の新しい機能の利用ユースケースが広がりました。

補足 : 現在 (2023 年 12 月 22 日)、Aurora PostgreSQLでは、Data API は Aurora Serverless v2、Aurora Serverless v1、およびプロビジョニングされたデータベースでサポートされています。Aurora MySQL では、Data API は Aurora Serverless v1 データベースでのみサポートされています

Build a GraphQL API for your Amazon Aurora MySQL database using AWS AppSync and the RDS Data API」では Aurora Serverless v1 (MySQL) に対して AWS AppSync と RDS Data API を使って GraphQL API を構築しているので、今回は、Amazon Aurora Serverless v2 (PostgreSQL) で AWS AppSync の新しい機能が使えるのかどうか検証してみます

内容に不備、あるいは追加情報などありましたら、気軽にコメント頂けますと幸いです!

Step1. Amazon Aurora Serverless v2 (PostgreSQL) の作成

まず、Aurora Serverless v2 (PostgreSQL) の DB クラスタを作成します。手順については「Aurora PostgreSQL DB クラスターの作成と接続」を参考にしてください。

DB クラスターの作成が完了したら、テーブルの作成を行います。以下のようなテーブル定義にします。

CREATE TABLE conversations (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE messages (
  id UUID PRIMARY KEY,
  conversation_id INT NOT NULL,
  sub UUID NOT NULL,
  body TEXT NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (conversation_id) REFERENCES conversations(id)
);

CREATE TABLE conversation_participants (
  conversation_id INT NOT NULL,
  sub UUID NOT NULL,
  last_read_at TIMESTAMP WITH TIME ZONE,
  PRIMARY KEY (conversation_id, sub),
  FOREIGN KEY (conversation_id) REFERENCES conversations(id)
);

Step2. Secret Manager に DB 認証情報の保存

AWS AppSync は Amazon Aurora クラスターに接続する際に Secret Manager に保存されたシークレットを使用します。シークレットのタイプを「Amazon RDS データベースの認証情報」を選択肢、認証情報をデータベースを選択します。それ以外はデフォルトのまま、作成に進みます。

スクリーンショット 2023-12-22 16.07.28.png

Step3. GraphQL API の作成

次に、GraphQL API を作成しましょう。GraphQL API データソースで「Amazon Aurora クラスターから始める - New」を選択し、次に進みます。

スクリーンショット 2023-12-22 13.22.27.png

次に、API 名にわかり易い名前を入力します。今回はデフォルトの「My AppSync API」としました。

スクリーンショット 2023-12-22 13.23.45.png

次にデータベースを選択します。

スクリーンショット 2023-12-22 16.13.17.png

「データベースを選択」をクリックすると接続したい Amazon Aurora クラスターが選択出来ます。AWS Secret Manager シークレットは Step2 で作成したものを選択します。入力が終えたら「インポート」をクリックします。

database.png

「インポート」をクリックするとイントロスペクションが開始され、下の画像のようにデータベースのテーブルがインポートされました。タイプ名はカスタマイズすることができるので、以下のように変更します。(画像は変更前です。)

  • Conversation_participants : Participant
  • Conversations : Conversation
  • Messages : Message

問題なければ、「次へ」をクリックします。

スクリーンショット 2023-12-22 13.31.13.png

スキーマの設定では、クエリのみを作成するか、クエリ、ミューテーション、サブスクリプションを作成するかを選択することが出来ます。今回は、「すべてのモデルに対してクエリ、ミューテーション、サブスクリプションを作成」を選択します。これで GraphQL API の作成は完了です!

スクリーンショット 2023-12-22 13.31.31.png

Step4. 動作確認

では、実際にどんなスキーマやリゾルバーが作成されたか見てみましょう。まずはスキーマを見てみましょう。

確認 1 : GraphQL スキーマ

以下が自動で生成されたスキーマ (一部抜粋) です。正直初めてやったときに結構感動しました。ただ、一点気になったこととしては created_atString になっていることです。Amazon Aurora PostgreSQL の場合、RDS Data API は常に UTC タイムゾーンの Aurora PostgreSQL データ型 TIMESTAMPTZ を返すのですが、AWS AppSync のスカラー型では TIMESTAMPTZ はサポートしておらず、かつ AWS AppSync はカスタムスカラーをサポートしていません。そのため、リゾルバー側で対応する必要がありそうです。

(今回は力尽きたのでリゾルバーはいじりません..またの機会をお楽しみに...)

type Conversation {
    name: String!
    created_at: String
    id: Int!
}

type ConversationConnection {
    items: [Conversation]
    nextToken: String
}

input CreateConversationInput {
    name: String!
    created_at: String
    id: Int!
}

input CreateMessageInput {
    created_at: String
    conversation_id: Int!
    id: ID!
    body: String!
    sub: ID!
}

input CreateParticipantInput {
    conversation_id: Int!
    last_read_at: String
    sub: ID!
}

input DeleteConversationInput {
    id: Int!
}

input DeleteMessageInput {
    id: ID!
}

input DeleteParticipantInput {
    conversation_id: Int!
    sub: ID!
}

type Message {
    created_at: String
    conversation_id: Int!
    id: ID!
    body: String!
    sub: ID!
}

type MessageConnection {
    items: [Message]
    nextToken: String
}

type Participant {
    conversation_id: Int!
    last_read_at: String
    sub: ID!
}

type ParticipantConnection {
    items: [Participant]
    nextToken: String
}

(〜〜〜〜〜〜〜〜〜省略〜〜〜〜〜〜〜〜〜)

type Mutation {
    createMessage(input: CreateMessageInput!): Message
    updateMessage(input: UpdateMessageInput!, condition: TableMessageConditionInput): Message
    deleteMessage(input: DeleteMessageInput!, condition: TableMessageConditionInput): Message
    createConversation(input: CreateConversationInput!): Conversation
    updateConversation(input: UpdateConversationInput!, condition: TableConversationConditionInput): Conversation
    deleteConversation(input: DeleteConversationInput!, condition: TableConversationConditionInput): Conversation
    createParticipant(input: CreateParticipantInput!): Participant
    updateParticipant(input: UpdateParticipantInput!, condition: TableParticipantConditionInput): Participant
    deleteParticipant(input: DeleteParticipantInput!, condition: TableParticipantConditionInput): Participant
}

type Query {
    getMessage(id: ID!): Message
    listMessages(filter: TableMessageFilterInput, limit: Int, nextToken: String): MessageConnection
    getConversation(id: Int!): Conversation
    listConversations(filter: TableConversationFilterInput, limit: Int, nextToken: String): ConversationConnection
    getParticipant(conversation_id: Int!, sub: ID!): Participant
    listParticipants(filter: TableParticipantFilterInput, limit: Int, nextToken: String): ParticipantConnection
}

type Subscription {
    onCreateMessage(
        created_at: String,
        conversation_id: Int,
        id: ID,
        body: String,
        sub: ID
    ): Message
        @aws_subscribe(mutations: ["createMessage"])
    onUpdateMessage(
        created_at: String,
        conversation_id: Int,
        id: ID,
        body: String,
        sub: ID
    ): Message
        @aws_subscribe(mutations: ["updateMessage"])
    onDeleteMessage(
        created_at: String,
        conversation_id: Int,
        id: ID,
        body: String,
        sub: ID
    ): Message
        @aws_subscribe(mutations: ["deleteMessage"])
    onCreateConversation(name: String, created_at: String, id: Int): Conversation
        @aws_subscribe(mutations: ["createConversation"])
    onUpdateConversation(name: String, created_at: String, id: Int): Conversation
        @aws_subscribe(mutations: ["updateConversation"])
    onDeleteConversation(name: String, created_at: String, id: Int): Conversation
        @aws_subscribe(mutations: ["deleteConversation"])
    onCreateParticipant(conversation_id: Int, last_read_at: String, sub: ID): Participant
        @aws_subscribe(mutations: ["createParticipant"])
    onUpdateParticipant(conversation_id: Int, last_read_at: String, sub: ID): Participant
        @aws_subscribe(mutations: ["updateParticipant"])
    onDeleteParticipant(conversation_id: Int, last_read_at: String, sub: ID): Participant
        @aws_subscribe(mutations: ["deleteParticipant"])
}

確認 2 : リゾルバー

次に、リゾルバーを見てみます。以下は createMessage のリゾルバーコードです。
JavaScript で書かれているので読みやすいですね。

import { util } from '@aws-appsync/utils';
import { insert, createPgStatement, toJsonObject } from '@aws-appsync/utils/rds';

/**
 * Puts an item into the messages table using the supplied input.
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
    const { input } = ctx.args;
    const insertStatement = insert({
        table: 'messages',
        values: input,
        returning: '*',
    });
    return createPgStatement(insertStatement)
}

/**
 * Returns the result or throws an error if the operation failed.
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
    const { error, result } = ctx;
    if (error) {
        return util.appendError(
            error.message,
            error.type,
            result
        )
    }
    return toJsonObject(result)[0][0]
}

確認 3 : GraphQL スキーマの作成タイミング

最後に AWS AppSync の GraphQL スキーマ作成のタイミングを確認してみました。
conversations テーブルに status というカラムを追加してみました。

ALTER TABLE conversations ADD COLUMN status VARCHAR(100);

これで、 AWS AppSync コンソール側でスキーマの反映ができれば...と思いましたが、現時点 (2023 年 12 月 22 日) 時点ではスキーマの反映は GraphQL API を新規作成するタイミングだけでした。既存の GraphQL API にデータソースを新規追加する際にもスキーマは反映されません。今後の Update に期待ですね。

まとめ

今回は、AWS AppSync の RDS Data API を使用して Amazon Aurora Serverless v2 (PostgreSQL) クラスター内のデータベースに対してイントロスペクションを行うことで、検出したテーブルに適合した GraphQL API のインターフェイスを作成する機能を試してみました。スカラーの問題があったため、少し手を加える必要がありますが、爆速で構築できたことに驚きました。

AWS AppSync は 2023 年に多くのアップデートがあり、これからが楽しみです。

明日から旅行に行くのでここでおしまいとします。続報をお楽しみに

11
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?