Designing and Engineering "遊び心"駆動開発

Playful IT
2/5 6:44

サーバーレスを学ぶのにAWS Amplifyを使うのをやめた件

AWS Amplifyとの出会い

目黒にあるAWS Loft Tokyoにて行われたBlack Beltセミナーの公開収録に参加して初めてAmplifyを知った。

AWS BlackBeltセミナー Amplify@AWS Loft 2018/11/6

それまでAWSで使ったことがあるのはEC2やRDS、S3、CloudFront、Route 53くらいだったので、「サーバーレス」や「マネージドサービス」というものについて全然理解できていなかった。CloudFormationも名前くらいしか知らなかったので、インフラのコード管理等ももちろんやったことがなかった。

そんな中この公開収録に参加して、「こんな便利なものがあるのか!」という感覚と共に、絶対使えるようになりたいと思うようになった。

ひたすらAmplifyについて学ぶ

それからというもの、時間を見つけてはAmplifyのドキュメントを読み漁り、適当に作ったAngularプロジェクトに導入しようと試行錯誤を続けた。

実際に開発しているアプリのバックエンドの構築に使うことも見据えて、ユーザー認証や権限管理まで踏み込んで調べたりもした。

AppSyncの認証モードがCognitoユーザープールを使ったりIAMを使ったりできることや、それぞれの使い分け方法なども理解することができた。IAMロールについても使う機会がなかったのでここでがっつり時間をとって概念を理解できたことが非常に大きく、その後のCodePipelineやCodeBuildなどの勉強の際にも大変役立った。

AmplifyやAppSyncがわかってきて、学びの中心がアプリケーションの設計に移った

Amplifyが提供する機能をどうやってJavaScriptアプリケーションから呼び出すのかがだいたいわかってきたところで、実際にそれらを使ってどうアプリケーションを開発していくかに関心が移った。

  • AppSyncのリゾルバー(VTL)はどのように書いたら良いのか?
  • AppSyncのスキーマ(GraphQL)はどのように定義したら良いのか?
  • 要件を満たすにはDynamoDBにどのようにデータを入れたらいいのだろうか?

Amplify経由ではスムーズに学べないと感じるようになった

Amplify(@amplify/cli)を使ってバックエンドを構築する場合、マネジメントコンソールからバックエンドの設定を変更するのは好ましくない。プロジェクトのリポジトリにバックエンドの設定を書いておいて、$ amplify pushすることで適用していくフローを作れるのが利点なのに、直接バックエンドを変更してしまうとその旨味がなくなってしまう。

これはCloudFormationを使ってバックエンドを構築する場合も同様。

そうなってくると、DynamoDBのテーブル設計で学んだことを試そうと思っても、それをAmplifyでどう表現するかがワンクッション必要になってしまう。

Amplifyでは、DynamoDBにどんなテーブルを作って、どのようにデータを書き込むのかすらスキーマに書いた@model@connection等のディレクティブで設計していく。

つまり、目指したいDynamoDBのスキーマ設計がある場合、それをどうAmplifyのルールに則ったスキーマで表現するかを知る必要がある。表現する方法がないかもしれない。ない場合はAmplifyのGraphQL Transform機能を使って自分でスキーマファイルからGraphQLスキーマやAppSyncのリゾルバーを生成する処理を書くしかないらしい。

Amplifyをはじめとした自動化ツールは、手作業でできることを自動化するツールとして使うのが良さそう

2ヶ月に及ぶ技術勉強でわかったのが、いきなりAmplifyを使ってバックエンドの設計を学ぶのはあまり良い手ではないということ。

MySQLがよくわかっていない人がRuby on RailsでデータベースにMySQLを使うような感じなのかもしれない。ある程度の要件まではフレームワークの機能に則ってできるかもしれないが、フレームワークを使わずにやる方法がわかってもそれをすぐに試せないと学ぶのが難しくなってしまう。

Amplifyをはじめとした、バックエンドを自動で構築するプロダクトはあくまで自分の作りたいアプリケーションの要件を満たせそうだったら取り入れるという使い方が良さそう。

慣れている人だったら「何を当たり前のことを」と思うかもしれないし、「いや、むしろ使ったほうがいい」と思うかもしれない。

自分なりに理解が進むと思う方法をとり続けてなんとか学んでいけたらと思う。

AWS Amplifyを使わずにWebアプリのバックエンドをサーバーレスで構築してみる

ここからは切り替えて、Amplifyを使わずにWebアプリのバックエンドを構築してみる。 準備するものは以下の通り。

サービス 目的
AppSync API Web APIを提供するためのGraphQLのエンドポイント
DynamoDB テーブル アプリケーションのデータを格納するためのNoSQLデータベース

サーバーレスアプリケーションを構築するのにこれらのサービスが使えるということもAmplifyが自動的にやっていることを眺めることで知れたので、そういう意味でもAmplifyには助けられた。

Amplifyではユーザー認証でCognitoユーザープールを選択することが多かったが、今回はよりシンプルな構成でサーバーレスアプリケーションの設計に集中するため、AppSyncの認証モードをAPI KeyにすることでCognitoを使わない構成にしてみる。

AppSyncのAPIを作成する

AWSマネジメントコンソールのAppSyncの管理画面を開いて、「Create API」をクリックする。

サーバーレスを学ぶのにAWS Amplifyを使うのをやめた

いくつかの作成方法とテンプレートからAPIを作成できるようなので、ウィザードを使って作成してみる。

サーバーレスを学ぶのにAWS Amplifyを使うのをやめた

モデル名やカラムを設定していく。今回は簡単なツイッター的なアプリを作ろうと思うので、PostモデルにString型のcontentカラムを追加して必須項目としてみた。

サーバーレスを学ぶのにAWS Amplifyを使うのをやめた件

最後にAppSyncのAPIの名称を指定する。

サーバーレスを学ぶのにAWS Amplifyを使うのをやめた件

作成を実行すると、AppSyncのAPIとDynamoDBのテーブルが作成された。

サーバーレスを学ぶのにAWS Amplifyを使うのをやめた件

サーバーレスを学ぶのにAWS Amplifyを使うのをやめた件

AppSyncのスキーマを見ると、Postモデルの読み込み・作成・更新・削除に必要なQuery・Mutation・Subscriptionが自動で定義されていた。

QueryとMutationについてはリゾルバーも自動で作成してくれたみたい。今までAmplify経由でしかAppSyncのAPIを作ってこなかったので、今回ウィザードを使うことでここまで作成してくれるということを学べた。

フロントエンドのアプリケーションを作成する

AppSyncに対してAPI Keyで認証を行なってPostの読み込み・作成・更新・削除を行うアプリケーションを作成する。 前回までの勉強で使っていたIonic 4(Angular 7)をまた使おうと思う。

簡単にWebアプリケーションの雛形をそれっぽいUIで作れるから重宝する。



$ ionic start

# プロジェクト名を設定
? Project name: PostApp

# テンプレートを選択
? Starter template: (Use arrow keys)
❯ blank    | A blank starter project
  sidemenu | A starting project with a side menu with navigation in the content area
  tabs     | A starting project with a simple tabbed interface

# Appflowを使うかどうか設定
? Install the free Ionic Appflow SDK and connect your app? (Y/n) n

続いてAppSyncを使うために必要なパッケージをインストールする。



# プロジェクトのディレクトリに移動
$ cd PostApp

# AWS AppSync JavaScript SDKをインストール
$ npm install --save aws-appsync graphql-tag

前回まではAngularのモジュール機能を利用して画面ごとにモジュールを分割したりしていたが、シンプルにするために一旦それもやめてAppModuleのみで書いてみる。



$ tree src/app
src/app
├── app-routing.module.ts
├── app.component.html
├── app.component.ts
├── app.module.ts
├── graphql
│   ├── mutations.ts
│   ├── queries.ts
│   └── subscriptions.ts
├── models
│   └── post.model.ts
├── pages
│   ├── task-detail
│   │   ├── task-detail.page.html
│   │   ├── task-detail.page.scss
│   │   └── task-detail.page.ts
│   └── task-list
│       ├── task-list.page.html
│       ├── task-list.page.scss
│       └── task-list.page.ts
└── services
    ├── appsync.service.ts
    └── post.service.ts

AppSyncとの通信を行う部分はAppSyncServiceにまとめた。



import { Injectable } from '@angular/core';
import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync';

@Injectable({
  providedIn: 'root'
})
export class AppSyncService {

  _client;

  constructor() {
    this._client = new AWSAppSyncClient({
      url: 'https://XXXXXXXXXXXXX.appsync-api.us-east-1.amazonaws.com/graphql', // AppSyncのエンドポイント
      region: 'us-east-1',  // AppSyncのリージョン
      auth: {
        type: AUTH_TYPE.API_KEY,
        apiKey: 'XXXXXXXXX', // AppSyncのAPIキー
      }
    });
  }

  get client() {
    return this._client;
  }
}

実際につぶやき(Postモデル)を扱うのはAppSyncServiceをDIしたPostServiceで行うようにした。



import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import gql from 'graphql-tag';
import { Post } from '../models/post.model';
import { AppSyncService } from './appsync.service';
import * as queries from '../graphql/queries';
import * as mutations from '../graphql/mutations';
import * as subscriptions from '../graphql/subscriptions';

@Injectable({
  providedIn: 'root'
})
export class PostService {

  postsSubject$ = new BehaviorSubject<Post[]>([]);

  constructor(
    private appSyncService: AppSyncService,
  ) {

    // 初回の読み込み
    this.appSyncService.client.query({
      query: gql(queries.listPosts),
    }).then(data => {
      const posts = data.data.listPosts.items.map(record => Post.parseJson(record));
      this.postsSubject$.next(posts);
    });

    // 追加を監視
    this.appSyncService.client.subscribe({
      query: gql(subscriptions.onCreatePost),
    }).subscribe(data => {
      const post = Post.parseJson((<any>data).data.onCreatePost);
      this.postsSubject$.next([
        ...this.postsSubject$.getValue(),
        post,
      ]);
    });

    // 削除を監視
    this.appSyncService.client.subscribe({
      query: gql(subscriptions.onDeletePost),
    }).subscribe(data => {
      const post = Post.parseJson((<any>data).data.onDeletePost);
      this.postsSubject$.next([
        ...this.postsSubject$.getValue().filter(p => p.id !== post.id),
      ]);
    });
  }

  get posts$(): Observable<Post[]> {
    return this.postsSubject$.asObservable();
  }

  /**
   * つぶやきを作成
   */
  createPost(content: string): Promise<any> {
    return this.appSyncService.client.mutate({
      mutation: gql(mutations.createPost),
      variables: {
        input: {
          content: content,
        }
      },
    });
  }

  /**
   * つぶやきを削除
   */
  deletePost(id: string): Promise<any> {
    return this.appSyncService.client.mutate({
      mutation: gql(mutations.deletePost),
      variables: {
        input: {
          id: id,
        }
      },
    });
  }
}

動作画面

サーバーレスを学ぶのにAWS Amplifyを使うのをやめた件

サーバーレスを学ぶのにAWS Amplifyを使うのをやめた件

まとめ

Amplifyを使わずにAppSyncのAPIを作成して、DynamoDBのスキーマを設計して、GraphQLのスキーマとリゾルバーを作ってみた。今までAmplifyがいい感じに作ってくれていた部分を自分で書くことで、理解が甘い部分が一気に明確になった感がある。

今回はオンラインの状態でアプリ起動時に全てのつぶやきを取得してきて、追加・削除のイベントを購読して自動的に同期するように実装した。つぶやきの作成と削除も実装したので、最低限遊べるアプリが出来上がった。

DynamoDBは当初はソートキーとしてcreated_atというカラムを作ってみたものの、結局レコードの更新や削除のときの指定方法がよくわからずAppSyncのAPIごと作り直したりもした。

結局手を動かして色々試してみるのが自分的に一番効率よく学べるので、こんな感じでちょっとしたサンプルアプリを量産していきたい。

関連記事
2/4 10:59

AWS AmplifyのAPI.graphqlではなくAWSAppSyncClientでAppSyncにアクセスしてみる

AWS Amplifyが提供するGraphQL AWS Amplifyを使ってAppSyncを利用する場合、 import { AmplifyService } from 'aws-amplify-angular'; で読み込んだAmplifyServiceをDI…

2/4 7:32

AWS AmplifyでAppSyncのGraphQLスキーマを作成する

目指すもの まずはログインしているユーザーがつぶやきを投稿できるだけのシンプルなアプリケーションを目指す。Amazon Cognito上ではユーザーは複数作成されるが、つぶやきにはユーザーは関連付けない。 学びたいこと AWS Amplifyで定義したGraphQL…

2/4 6:52

AWS Amplifyのログイン状態に応じてIonic 4の画面を切り替える

前回まで Ionic 4のアプリケーションを作成して、AWS Amplifyを使ってバックエンドを構築した。ログイン周りのビューはAWS Amplifyで提供される <amplify-authenticator> コンポーネントを利用して構築した。 Angular…

2/2 11:08

AWS AmplifyをIonic 4で使うときのモジュール分割を考える

前回まで AWS AmplifyをIonic 4で使ってみる Ionic 4(Angular 7)のアプリケーションを作成して、AWS AmplifyでAWS上にAWS AppSyncとAmazon Cognitoを使ったAPI…

2/1 9:52

AWS AmplifyをIonic 4で使ってみる

あらまし AWSのマネージドサービスをうまく活用することで、高い可用性を持ったアプリケーションを開発できる。2019年はスマホアプリ・PWAのバックエンドにAWS…

プロフィール