Amplify ライブラリを使って既存の AWS バックエンドと簡単に統合してみる

2021-05-07
デベロッパーのためのクラウド活用方法

鈴木 貴博

Amplify な皆様、こんにちは ! ソリューションアーキテクトの鈴木貴博と申します ! 
AWS Amplify を使い、フルスタックなアプリケーションを開発されているお客様のよくある課題として、こういった課題がよくあると思います。

  • バックエンドの構築は AWS クラウド開発キット (CDK) や AWS CloudFormation を利用したいので、Amplify CLI は使用しない
  • フロントエンドチームとバックエンドチームが別れているため、フロントエンドチームからはバックエンドリソースの構築は行えないようにしたいが、Amplify Libraries を使って簡単に AWS Amplify のバックエンドリソースと統合したい
  • 既に構築済みの AppSync、Cognito 等のリソースがあるので、Amplify Libraries を使ってサクッとバックエンドと統合したい

Amplify は Amplify ライブラリや Amplify CLI、Amplify Console から構成される、フルスタックなフレームワークのため、フロントエンドもバックエンドも一元的に管理しないといけないというイメージが強いかもしれません。しかし、Amplify では既存のリソースを Import する機能も含まれているため、必ずしも全てのリソースを使用しなくてはいけないというわけではなく、選択的に必要な Amplify のコンポーネントのみを使用することが可能です。例えば・・・

  • バックエンド環境は CDK で作成するため、Amplify ライブラリだけ使用して、CDK で作成したバックエンドと統合する
    • 既存の シングルページアプリケーション (SPA) のホスティングのために、Amplify Console のみを使用する
    • オフラインでのストレージ機能のために、バックエンドの構築は行わず、Amplify ライブラリの DataStore の機能のみを使用する

こういったように、Amplify は実は必要な部分だけをいいとこ取りできる柔軟なフルスタックフレームワークとなっています。

本記事では、Andorid アプリケーションと React アプリケーションにおいて、Amplify ライブラリを使って、マネジメントコンソールから作成した AWS AppSync と統合する方法を紹介します。

このクラウドレシピ (ハンズオン記事) を無料でお試しいただけます »

毎月提供されるクラウドレシピのアップデート情報とともに、クレジットコードを受け取ることができます。 


1. 事前準備

本記事では以下の環境構築が済んでいる前提としています。後ほど説明しますが、Amplify CLI はクライアント側のモデル生成のために使用するので、Amplify CLI を使って、バックエンドリソースの作成といったことはしないため、IAM User の作成の必要もありません。

  • AWS アカウント
  • AWS Amplify CLI v4.45.0
  • Android
    • Andorid Studio v4.0^
    • Andorid SDK API level 29
  • Nodejs v14.14.0

まずは AWS AppSync をコンソールを使って作成します。

API を作成する をクリックし、サンプルプロジェクトから開始する から イベントアプリ を選択して、開始 をクリックします。

クリックすると拡大します

続いて、API 名 に任意の名前を入力し、作成 をクリックします。

クリックすると拡大します


2. Amplify ライブラリ (Andorid) x 既存の AppSync

それでは早速、Andorid アプリケーション (Kotlin) を作成していきます。

2-1. Android アプリケーションのセットアップ

Android Studio を開き、Create New Project を選択します。

クリックすると拡大します

Template は Empty Activity を選択します。

クリックすると拡大します

2-2. アプリケーションへ Amplify を追加する

まず、Amplify for Andorid を作成した Android アプリケーションに追加していきます。Gradle Scripts の **build.gradle (Project)** に以下を追記します。

Kotlin

buildscript {
   repositories {
       google()
       jcenter()
   }

   dependencies {
       classpath 'com.android.tools.build:gradle:4.1.2'

       // Add this line into `dependencies` in `buildscript`
       classpath 'com.amplifyframework:amplify-tools-gradle-plugin:1.0.2'
   }
}

allprojects {
   repositories {
       google()
       jcenter()
   }
}

// Add this line at the end of the file
apply plugin: 'com.amplifyframework.amplifytools'

続いて、Gradle Scripts の **build.gradle (Module)** に以下を追加し、Gradle Sync を実行します。

Kotlin

dependencies {
    implementation 'com.amplifyframework:aws-api:1.17.1'
}

Build が終了し、Project View に切り替え、アプリケーションのディレクトリに以下のようなフォルダが作成されていることを確認します。

amplify
├── #current-cloud-backend
│   └── amplify-meta.json
├── README.md
├── backend
│   ├── amplify-meta.json
│   ├── api
│   │   └── amplifyDatasource
│   │       ├── parameters.json
│   │       ├── schema.graphql
│   │       ├── stacks
│   │       │   └── CustomResources.json
│   │       └── transform.conf.json
│   └── backend-config.json
├── cli.json
└── team-provider-info.json

クリックすると拡大します

2-3. AppSync の GraphQL スキーマを取得する

Amplify ライブラリの GraphQL モジュールでは GraphQL のレスポンスタイプのマッピングのために、モデルクラスを生成する必要があります。そのため、事前準備で作成した AppSync のスキーマをダウンロードし、Amplify CLI でコード生成していきます。

再び、AWS AppSync のコンソールに戻り、スキーマ のセクションから スキーマをエクスポート を選択し、ドロップダウンから schema.graphql を選択します。

クリックすると拡大します

ダウンロードした schema.graphql を先程、生成した  amplify/backend/api/amplifyDatasource/schema.graphql に配置します。

2-4. Amplify 用の GraphQL Directrive を追加する

ダウンロードしてきた AppSync のスキーマは以下のような感じです。

GraphQL

schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}

type Comment {
  #  A unique identifier for the comment.
  commentId: String!
  #  The comment's content.
  content: String!
  #  The comment timestamp. This field is indexed to enable sorted pagination.
  createdAt: String!
  #  The id of the comment's parent event.
  eventId: ID!
}

type CommentConnection {
  items: [Comment]
  nextToken: String
}

type Event {
  #  Paginate through all comments belonging to an individual post.
  comments(limit: Int, nextToken: String): CommentConnection
  description: String
  id: ID!
  name: String
  when: String
  where: String
}

type EventConnection {
  items: [Event]
  nextToken: String
}

type Mutation {
  #  Comment on an event.
  commentOnEvent(content: String!, createdAt: String!, eventId: ID!): Comment
  #  Create a single event.
  createEvent(description: String!, name: String!, when: String!, where: String!): Event
  #  Delete a single event by id.
  deleteEvent(id: ID!): Event
}

type Query {
  #  Get a single event by id.
  getEvent(id: ID!): Event
  #  Paginate through events.
  listEvents(filter: TableEventFilterInput, limit: Int, nextToken: String): EventConnection
}

type Subscription {
  subscribeToEventComments(eventId: String!): Comment @aws_subscribe(mutations : ["commentOnEvent"])
}

input TableBooleanFilterInput {
  eq: Boolean
  ne: Boolean
}

...

このままでは Amplify CLI でのコード生成ができないため、以下の項目を追加します。

  • 各 Type に @model (queries: null, mutations: null, subscriptions: null) を追加する
  • Model 間で 1 対多のリレーションが必要な項目には @connection を追記する


最終的な schema.graphql は以下のようになります。

GraphQL

type Comment 
# @modelを追加
@model(queries: null, mutations: null, subscriptions: null) {
    commentId: String!
    content: String!
    createdAt: String!
    eventId: ID!
}

type CommentConnection 
# @modelを追加
@model {
    items: [Comment] @connection # @connectionを追加
    nextToken: String
}

type Event 
# @modelを追加
@model(queries: null, mutations: null, subscriptions: null) {
    comments(limit: Int, nextToken: String): CommentConnection @connection # @connectionを追加
    description: String
    id: ID!
    name: String
    when: String
    where: String
}

type EventConnection 
# @modelを追加
@model {
    items: [Event] @connection # @connectionを追加
    nextToken: String
}

type Mutation {
    commentOnEvent(content: String!, createdAt: String!, eventId: ID!): Comment
    createEvent(description: String!, name: String!, when: String!, where: String!): Event
    deleteEvent(id: ID!): Event
}

type Query {
    getEvent(id: ID!): Event
    listEvents(filter: TableEventFilterInput, limit: Int, nextToken: String): EventConnection
}

type Subscription {
    subscribeToEventComments(eventId: String!): Comment @aws_subscribe(mutations : ["commentOnEvent"])
}

input TableBooleanFilterInput {
    eq: Boolean
    ne: Boolean
}

input TableEventFilterInput {
    description: TableStringFilterInput
    id: TableIDFilterInput
    name: TableStringFilterInput
    when: TableStringFilterInput
    where: TableStringFilterInput
}

input TableFloatFilterInput {
    between: [Float]
    contains: Float
    eq: Float
    ge: Float
    gt: Float
    le: Float
    lt: Float
    ne: Float
    notContains: Float
}

input TableIDFilterInput {
    beginsWith: ID
    between: [ID]
    contains: ID
    eq: ID
    ge: ID
    gt: ID
    le: ID
    lt: ID
    ne: ID
    notContains: ID
}

input TableIntFilterInput {
    between: [Int]
    contains: Int
    eq: Int
    ge: Int
    gt: Int
    le: Int
    lt: Int
    ne: Int
    notContains: Int
}

input TableStringFilterInput {
    beginsWith: String
    between: [String]
    contains: String
    eq: String
    ge: String
    gt: String
    le: String
    lt: String
    ne: String
    notContains: String
}

2-5. GraphQL Code Generation により、Java のモデルクラスを生成する

これで、AppSync のスキーマから Java のモデルクラスを生成する準備が整ったので、Gradle Task のドロップダウンから modelgen を選択し、実行します。 もしくは  Terminal から amplify codegen models によってモデルクラスを生成することも可能です。

クリックすると拡大します

Build が完了すると以下のようなフォルダが作成されているのが確認できます。

app/src/main/java/com/amplifyframework/
└── datastore
    └── generated
        └── model
            ├── AmplifyModelProvider.java
            ├── Comment.java
            ├── CommentConnection.java
            ├── Event.java
            ├── EventConnection.java
            ├── Mutation.java
            ├── Query.java
            ├── Subscription.java
            ├── TableBooleanFilterInput.java
            ├── TableEventFilterInput.java
            ├── TableFloatFilterInput.java
            ├── TableIDFilterInput.java
            ├── TableIntFilterInput.java
            └── TableStringFilterInput.java

2-6. Amplify Libraries の初期設定

続いて、Amplify Libraries の初期設定を行います。今回は既存の AWS AppSync を利用するため、自身で AppSync のエンドポイントや認証方法などの情報を Amplify.configure に渡してあげる必要があります。こちらのドキュメントを参考に app/src/main/res/raw/amplifyconfiguration.json というファイルを作成し、以下を追記します。

xxxxxxxxの部分をそれぞれ、自身のAppSyncのエンドポイント、API KEYに書き換えてください。

{
    "api": {
        "plugins": {
            "awsAPIPlugin": {
                "[API NAME]": {
                    "endpointType": "GraphQL",
                    "endpoint": "https://xxxxxxxxxxxxx.appsync-api.ap-northeast-1.amazonaws.com/graphql",
                    "region": "ap-northeast-1",
                    "authorizationType": "API_KEY",
                    "apiKey": "xxxxxxxxxxxxxx"
                }
            }
        }
    }
}

ここでは、コンソールのサンプルから作成した、API KEY 認証の AppSync を追加しましたが、その他のリソースも Amplify ライブラリと統合したいという場合は以下のリンクから、必要なパラメータを確認し、同様に amplifyconfiguration.json に追記してください。

続いて、以下のように Amplify ライブラリの初期化のために、以下を MainActivity.kt に追加します。

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    try {
        Amplify.addPlugin(AWSApiPlugin())
        Amplify.configure(applicationContext)
        Log.i("MyAmplifyApp", "Initialized Amplify")
    } catch (error: AmplifyException) {
        Log.e("MyAmplifyApp", "Could not initialize Amplify", error)
    }
    setContentView(R.layout.activity_main)
}

2-7. Amplify ライブラリを使って、Query してみる

最後に、実際に Amplify ライブラリを使って、AppSync の GraphQL API をクエリしてみます。既存の AppSync を使用する場合、Query、Mutation 等は定義されているはずなので、自動生成される GraphQL リクエストは利用できません。そのため、以下のようにカスタムの GraphQL リクエストを記述する必要があります。

多少、手間ではありますが、カスタムの GraphQL リクエストの方が 1) データ転送量削減のため、必要なサブセットのみを取得可能。 2) Query に応じて、必要な深さのネストしたオブジェクトを取得可能。 3) 複数の GraphQL オペレーションを単一 GraphQL リクエストとして扱うことが可能。 といったようなメリットを享受できるので、フロントエンドアプリケーションで使用するデータを見直し、本当に必要な GraphQL リクエストを考えてみてください !

Create

GraphQL

private fun createEventRequest(name: String, description: String, `when`: String, where: String): GraphQLRequest<Event> {
    val document = ("mutation createEvent(\$name: String!, \$description: String!, \$when: String!, \$where: String!) { "
            + "createEvent(name: \$name, description: \$description, when: \$when, where: \$where) { "
            + "id "
            + "name "
            + "}"
            + "}")
    return SimpleGraphQLRequest(
            document,
            mapOf("name" to name, "description" to description, "when" to `when`, "where" to where),
            Event::class.java,
            GsonVariablesSerializer())
}

public fun createEvent(view: View){
    Amplify.API.mutate(createEventRequest("xxxxxxx", "xxxxxxx", "xxxxxxxxxx", "xxxxxxx"),
            { Log.d("MyAmplifyApp", "Query Result = ${it.data}") },
            { Log.e("MyAmplifyApp", "Create failed", it) }
    )
}

Get

GraphQL

private fun getEventRequest(id: String): GraphQLRequest<Event> {
    val document = ("query getEvent(\$id: ID!) { "
                + "getEvent(id: \$id) { "
                    + "id "
                    + "name "
                    + "description "
                    + "comments {"
                        + "items {"
                            + "commentId "
                            + "content "
                        + "}"
                    + "}"
                + "}"
            + "}")

    return SimpleGraphQLRequest(
            document,
            mapOf("id" to id),
            Event::class.java,
            GsonVariablesSerializer())
}

public fun getEvent(view: View){
    Amplify.API.query(getEventRequest("xxxxxxxxxxxxxx"),
    { Log.i("MyAmplifyApp", "Query results = ${it.data}") },
    { Log.e("MyAmplifyApp", "Query failed", it) }
    )
}

List

GraphQL

private fun listEventRequest(limit: Int?, nextToken: String?): GraphQLRequest<EventConnection> {
    val document = ("query listEvents(\$limit: Int, \$nextToken: String) { "
                + "listEvents(limit: \$limit, nextToken: \$nextToken) { "
                    + "items { "
                        + "id "
                        + "name "
                        + "when "
                        + "where "
                        + "description "
                    + "} "
                    + "nextToken "
                + "} "
            + "} ")

    return SimpleGraphQLRequest(
            document,
            mapOf("limit" to limit, "nextToken" to nextToken),
            EventConnection::class.java,
            GsonVariablesSerializer())
}

public fun listEvent(view: View){
    Amplify.API.query(listEventRequest(limit = 5, nextToken = null),
            {Log.i("Amplify List Query", "Query result = ${(it.data)}")},
            { Log.e("MyAmplifyApp", "Query failed", it) }
    )
}

Delete

GraphQL

private fun deleteEvent(id: String): GraphQLRequest<Event> {
    val document = ("mutation DeleteEvent(\$id: ID!) { "
            + "deleteEvent(id: \$id) { "
            + "name "
            + "id "
            + "}"
            + "}")

    return SimpleGraphQLRequest(
            document,
            mapOf("id" to id),
            Event::class.java,
            GsonVariablesSerializer ())
}

public fun deleteEvent(view: View){
    val request = deleteEvent("xxxxxxxxxxxxxxx")
    Amplify.API.mutate(request,
            { Log.i("MyAmplifyApp", "Query result = ${it.data}") },
            { Log.e("MyAmplifyApp", "Query failed", it) }
    )
}

以上が Android アプリケーションでの、Amplify ライブラリと既存の AppSync を統合する方法でした。ここからは、Javascript での、Amplify ライブラリと既存の AppSync を統合する方法を紹介したいと思います。


3. Amplify ライブラリ (Javascript) x 既存の AppSync

今回は例として、React アプリケーションを使用します。

3-1. React アプリケーションのセットアップ

まずは以下のコードを実行して、React アプリケーションを作成します。

npx create-react-app amplifylib-appsync --template typescript
cd amplifylib-appsync

続いて、必要な依存関係をインストールします。

yarn add aws-amplify @aws-amplify/api

3-2. AppSync のスキーマから Typescript のコードを自動生成

まず、AWS AppSync のコンソールの、スキーマ のセクションから スキーマをエクスポート を選択し、ドロップダウンから schema.graphql を選択します。

クリックすると拡大します

ダウンロードした schema.graphql をプロジェクトのルートディレクトリに配置します。

amplifylib-appsync
├── README.md
├── node_modules
├── package.json
├── public
├── schema.graphql
├── src
├── tsconfig.json
└── yarn.lock

続いて、Amplify CLI を使用して、CRUD の GraphQL ステートメント等の Typescript のコードを自動生成します。以下のコードを実行してください。

➜ amplify codegen add
? Choose the type of app that you're building javascript
? What javascript framework are you using react
? Choose the code generation language target typescript
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.ts
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions 
Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
? Enter the file name for the generated code src/API.ts
? Do you want to generate code for your newly created GraphQL API Yes
✔ Generated GraphQL operations successfully and saved at src/graphql
✔ Code generated successfully and saved in file src/API.ts

これで、amplify init なしで、コード生成の機能だけを使用することができました。上記の amplify codegen add では Query の最大深さや、生成されるコードのパス等を設定しています。もし、変更が必要な場合は amplify codegen configure を実行してください。

3-3. 既存の AppSync を Amplify ライブラリに設定する

Amplify CLI を使用して、AppSync をプロビジョニングする場合、自動的に AppSync のエンドポイントや認証情報などを aws-exports.js というファイルに出力してくれます。しかし、今回は Amplify CLI を使用せずに既存の AppSync を利用するので、自身でこちらに記載されているパラメータを設定する必要があります。

src/App.tsx に以下のコードを追加します。

Amplify.configure({
  aws_appsync_region: "ap-northeast-1", 
  aws_appsync_graphqlEndpoint: "https://xxxxxx.appsync-api.ap-northeast-1.amazonaws.com/graphql",
  aws_appsync_authenticationType: "API_KEY", 
  aws_appsync_apiKey: "da2-xxxxxxxxxxxxxxxxxxxxxxxxxx",
});

上記の設定では直接、エンドポイント、認証方法を記載していますが、環境変数などに格納しておき、Amplify Console の環境変数で設定した環境変数をビルドのタイミングで引き渡すといった方法も、ステージごとにバックエンドのリソースを分離したいといったケースでは有効です。

クリックすると拡大します

3-4. Amplify ライブラリから GraphQL クエリを実行する

それでは実際に Amplify ライブラリを使って、GraphQL の Query していきたいと思います。

自動生成された GraphQL リクエストを使用した、Typescript での Amplify ライブラリの使用例は以下の通りです。

import {API, GraphQLResult} from '@aws-amplify/api';
import { CreateEventMutation, CreateEventMutationVariables, GetEventQuery, GetEventQueryVariables } from './API';

const createEvent = async (name: string, description: string, when: string, where: string) => {
  const result = API.graphql({
    query: createEvent,
    variables: {
      name: name,
      description: description,
      when: when,
      where: where
    } as CreateEventMutationVariables
  }) as GraphQLResult<CreateEventMutation>;
  return result;
}

const getEvent = async (id: string) => {
  const result = API.graphql({
    query: getEvent,
    variables: {
      id: id 
    } as GetEventQueryVariables
  }) as GraphQLResult<GetEventQuery>;
  return result;
}

また、自動生成された GraphQL リクエストではなく、カスタムの GraphQL リクエストを使用したいといった場合は、以下のように記述することもできます。

GraphQL

import {API} from '@aws-amplify/api';
import gql from 'graphql-tag'

const query = gql`
    query getEvent($id: ID!) {
      getEvent(id: $id) {
        id
        name
        description
        comments {
          items {
            commentId
            content
          }
        }
      }
    }
  }
`

const getEvent = async (id: string) => {
  const result = API.graphql({
    query: query,
    variables: {
      id: id 
    }
  })
  return result;
}

おわりに

いかがでしょうか ?
本記事では Amplify CLI によるバックエンド構築機能を利用せずに、Amplify ライブラリを使うことで簡単に既存の AppSync と統合する方法を紹介させていただきました。

Amplify はフルスタックなフレームワークだというイメージが強いかもしれませんが、今回のケースのように選択的に Amplify ライブラリだけを使用することで簡単に既存のバックエンドと統合するといったことも可能です。

そのため、Amplify CLI ではサポートしていないサービスやより詳細なパラメータ設定が必要な場合などは、CloudFormation、Terraform、CDK などでバックエンドリソースを管理し、フロントエンドアプリケーションからは Amplify ライブラリを使って統合することができます。

一方で、Amplify CLI を使ってバックエンドの構築をしないことで、いくつか Amplify から享受できるメリットが減ってしまうという側面があります。例えば、1) amplify mock コマンドを使ったローカルモックテスト。 2) amplify env 機能を使ったバックエンド環境の分離。 といった機能は Amplify CLI でのバックエンド構築が前提となります。

そのため、可能であれば Amplify CLI を使用して、バックエンドリソースも構築してしまったほうが、より Amplify の機能を活かすことができるはずです。しかし、全てを Amplify を使って構築するが必ずしも正しいというわけではないので、自身の要件に合わせて柔軟に使用する Amplify コンポーネントを選択していただければと思います。

Let's Amplify です !!!


builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます

著者プロフィール

鈴木 貴博 (Takahiro Suzuki / @kopkunka55 / @pujopujo55)
アマゾン ウェブサービス ジャパン合同会社
技術統括本部 エンタープライズソリューション本部 ストラテジック製造ビジネス部 ソリューションアーキテクト

AWS Japan でソリューションアーキテクトとして、主に製造業のお客様の技術支援をしています。山登りと AWS Amplify と Amazon EKS が好きです。

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する