2019.02.01
AWSのマネージドサービスをうまく活用することで、高い可用性を持ったアプリケーションを開発できる。2019年はスマホアプリ・PWAのバックエンドにAWSのマネージドサービス群を本格的に導入していきたいので、改めて丁寧に学び直すことにした。
フロントエンドアプリケーションの開発環境でAWS上にバックエンドを構築できるJavaScriptフレームワーク「AWS Amplify」を利用してバックエンドを構築していく。
フロントエンドのJavaScriptフレームワークはAngular 7を使い、UIフレームワークとして先日正式リリースされたIonic 4を使うことにした。
目指せ1ヶ月連続毎日1記事執筆!
# Node.jsのバージョンを指定
$ ndenv local v10.4.1
# Ionic CLIをインストール
$ npm install -g ionic
# Amplify CLIをインストール
$ npm install -g @aws-amplify/cli
# rehash
$ ndenv rehash
# Ionicプロジェクトをスタート
$ ionic start AwsAmplifyIonicSample
対話形式でIonicプロジェクトの設定を行う。
# テンプレートを選択:今回は「blank」を利用する
? 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を使うか選択:今回はAppflowは利用しない
? Install the free Ionic Appflow SDK and connect your app? (Y/n) n
# プロジェクトのディレクトリ
$ cd AwsAmplifyIonicSample
# 開発用HTTPサーバーを起動
$ ionic serve
自動的にブラウザでhttp://localhost:8100
が開く。Google ChromeのデベロッパーツールでiPhone 6/7/8モードにして確認してみる。
いよいよアプリケーションをAWS Amplifyで拡張していく。
$ amplify init
対話形式で初期設定を行う。
# プロジェクト名を指定
? Enter a name for the project AmplifySample
# 利用するエディタを指定
? Choose your default editor:
Sublime Text
Visual Studio Code
Atom Editor
IDEA 14 CE
Vim (via Terminal, Mac OS only)
Emacs (via Terminal, Mac OS only)
❯ None
# 開発するアプリケーションのプラットフォームを指定
? Choose the type of app that you're building (Use arrow keys)
android
ios
❯ javascript
# 利用するJavaScriptフレームワークがあれば指定
? What javascript framework are you using
angular
ember
❯ ionic
react
react-native
vue
none
# ソースコードのディレクトリを指定
? Source Directory Path: (src)
# 成果物が出来上がるディレクトリを指定
? Distribution Directory Path: (www)
# ビルドコマンドを指定
? Build Command: (npm run-script build)
# 開発用HTTPサーバー起動コマンドを指定
? Start Command: (ionic serve)
# AWSにアクセスするための認証情報を既存のプロファイルから選んで利用するか指定
? Do you want to use an AWS profile? (Y/n)
以上でAmplify Consoleの初期設定が完了した。Ionic 4では、CLIでプロジェクトを作成した時点でGit管理が開始されており、初回のコミットが行われている。せっかくなのでGitを使って差分を確認してみた。
$ git add .
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: .amplifyrc
new file: amplify/#current-cloud-backend/amplify-meta.json
new file: amplify/.config/aws-info.json
new file: amplify/.config/project-config.json
new file: amplify/backend/amplify-meta.json
AWS AmplifyではAmazon Cognitoを利用したユーザー認証が提供されている。$ amplify add
コマンドを使うことで数ステップでAmazon Cognitoをセットアップすることができる。セットアップは$ amplify push
コマンドを実行することでクラウド上に実際に構築される(次のAWS AppSyncをセットアップした後に実行する)。
$ amplify add auth
対話形式で初期設定を行う。デフォルトの設定も用意されているので、今回はそれを利用してみる。デフォルトの設定を利用する場合、特に追加で設定する項目はなかった。
Do you want to use the default authentication and security configuration? (Use arrow keys)
❯ Yes, use the default configuration.
No, I will set up my own configuration.
I want to learn more.
これでAmazon Cognitoの設定が準備できた。
$ amplify add api
こちらも対話形式で設定を行う。
# APIのタイプを指定する
? Please select from one of the below mentioned services (Use arrow keys)
❯ GraphQL
REST
# AWS AppSyncのAPI名を指定する
? Provide API name: (amplifysample)
# APIの認証方法を指定する
? Choose an authorization type for the API
API key
❯ Amazon Cognito User Pool
# GraphQLスキーマを既に作ってある場合はそれを利用することもできる(今回はなし)
? Do you have an annotated GraphQL schema? (y/N)
# 案内に沿ってGraphQLスキーマを作成することができる
? Do you want a guided schema creation? (Y/n)
# 3パターンのGraphQLスキーマ例から選ぶ
? What best describes your project:
Single object with fields (e.g., “Todo” with ID, name, description)
❯ One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)
Objects with fine-grained access control (e.g., a project management app with owner-based authorization)
# スキーマを編集することができる(今回はしない)
? Do you want to edit the schema now? (Y/n) n
これでAWS AppSyncの設定が準備できた。
$ amplify add
コマンドで設定したバックエンドの情報は$ amplify status
コマンドで確認することができる。Operationが「Create」になっているので、$ amplify push
するとAmazon CognitoのユーザープールとAWS AppSyncのAPIが作られる。
$ amplify status
| Category | Resource name | Operation | Provider plugin |
| -------- | --------------- | --------- | ----------------- |
| Auth | cognitoXXXXXXXX | Create | awscloudformation |
| Api | amplifysample | Create | awscloudformation |
$ amplify push
対話形式で最終確認やローカルのソース生成を行う。
# 最終確認が出る
| Category | Resource name | Operation | Provider plugin |
| -------- | --------------- | --------- | ----------------- |
| Auth | cognitoXXXXXXXX | Create | awscloudformation |
| Api | amplifysample | Create | awscloudformation |
? Are you sure you want to continue? (Y/n) Y
# JavaScriptアプリケーションで利用するためのGraphQLのコードを自動生成するか指定する
? Do you want to generate code for your newly created GraphQL API (Y/n)
# コード生成の種類を指定する
? Choose the code generation language target
angular
❯ 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 (Y/n)
# 生成された
? Enter the file name for the generated code (src/API.ts)
CloudFormation経由でバックエンドが設定されていく。
AWSのマネジメントコンソールを見ているとどんどんリソースが立ち上がっていくことがわかる。
$ amplify push
するとCloudFormation・DynamoDB・AppSync・Cognito・IAMロールが設定されるため、5分程度時間がかかる。
ここまで実行すると、アプリケーションのプロジェクトには以下のように大量のファイルが追加される。
$ git add .
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: .amplifyrc
new file: .graphqlconfig.yml
new file: amplify/#current-cloud-backend/amplify-meta.json
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Blog.posts.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Blog.posts.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Comment.post.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Comment.post.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.createBlog.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.createBlog.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.createComment.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.createComment.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.createPost.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.createPost.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.deleteBlog.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.deleteBlog.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.deleteComment.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.deleteComment.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.deletePost.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.deletePost.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.updateBlog.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.updateBlog.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.updateComment.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.updateComment.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.updatePost.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.updatePost.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Post.blog.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Post.blog.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Post.comments.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Post.comments.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.getBlog.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.getBlog.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.getComment.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.getComment.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.getPost.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.getPost.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.listBlogs.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.listBlogs.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.listComments.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.listComments.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.listPosts.request
new file: amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.listPosts.response
new file: amplify/#current-cloud-backend/api/amplifysample/build/schema.graphql
new file: amplify/#current-cloud-backend/api/amplifysample/cloudformation-template.json
new file: amplify/#current-cloud-backend/api/amplifysample/parameters.json
new file: amplify/#current-cloud-backend/api/amplifysample/schema.graphql
new file: amplify/#current-cloud-backend/auth/cognito1b62e7cb/cognito1b62e7cb-cloudformation-template.yml
new file: amplify/#current-cloud-backend/auth/cognito1b62e7cb/parameters.json
new file: amplify/.config/aws-info.json
new file: amplify/.config/project-config.json
new file: amplify/backend/amplify-meta.json
new file: amplify/backend/api/amplifysample/build/resolvers/Blog.posts.request
new file: amplify/backend/api/amplifysample/build/resolvers/Blog.posts.response
new file: amplify/backend/api/amplifysample/build/resolvers/Comment.post.request
new file: amplify/backend/api/amplifysample/build/resolvers/Comment.post.response
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.createBlog.request
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.createBlog.response
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.createComment.request
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.createComment.response
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.createPost.request
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.createPost.response
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.deleteBlog.request
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.deleteBlog.response
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.deleteComment.request
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.deleteComment.response
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.deletePost.request
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.deletePost.response
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.updateBlog.request
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.updateBlog.response
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.updateComment.request
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.updateComment.response
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.updatePost.request
new file: amplify/backend/api/amplifysample/build/resolvers/Mutation.updatePost.response
new file: amplify/backend/api/amplifysample/build/resolvers/Post.blog.request
new file: amplify/backend/api/amplifysample/build/resolvers/Post.blog.response
new file: amplify/backend/api/amplifysample/build/resolvers/Post.comments.request
new file: amplify/backend/api/amplifysample/build/resolvers/Post.comments.response
new file: amplify/backend/api/amplifysample/build/resolvers/Query.getBlog.request
new file: amplify/backend/api/amplifysample/build/resolvers/Query.getBlog.response
new file: amplify/backend/api/amplifysample/build/resolvers/Query.getComment.request
new file: amplify/backend/api/amplifysample/build/resolvers/Query.getComment.response
new file: amplify/backend/api/amplifysample/build/resolvers/Query.getPost.request
new file: amplify/backend/api/amplifysample/build/resolvers/Query.getPost.response
new file: amplify/backend/api/amplifysample/build/resolvers/Query.listBlogs.request
new file: amplify/backend/api/amplifysample/build/resolvers/Query.listBlogs.response
new file: amplify/backend/api/amplifysample/build/resolvers/Query.listComments.request
new file: amplify/backend/api/amplifysample/build/resolvers/Query.listComments.response
new file: amplify/backend/api/amplifysample/build/resolvers/Query.listPosts.request
new file: amplify/backend/api/amplifysample/build/resolvers/Query.listPosts.response
new file: amplify/backend/api/amplifysample/build/schema.graphql
new file: amplify/backend/api/amplifysample/cloudformation-template.json
new file: amplify/backend/api/amplifysample/parameters.json
new file: amplify/backend/api/amplifysample/schema.graphql
new file: amplify/backend/auth/cognito1b62e7cb/cognito1b62e7cb-cloudformation-template.yml
new file: amplify/backend/auth/cognito1b62e7cb/parameters.json
new file: amplify/backend/awscloudformation/nested-cloudformation-stack.yml
new file: src/API.ts
new file: src/aws-exports.js
new file: src/graphql/mutations.ts
new file: src/graphql/queries.ts
new file: src/graphql/schema.json
new file: src/graphql/subscriptions.ts
量があるのはAWS AppSyncのリゾルバー(amplify/backend/api/amplifysample/build/resolvers/*)。これらのリゾルバーを全て手作業で作成するとなると非常に骨が折れる。それらを一括で作成して、プロジェクトの一部としてGitリポジトリに含めることができるのはありがたい。
$ npm install --save aws-amplify aws-amplify-angular ionic-angular @types/node
例によって$ ionic serve
でローカルサーバーを起動するとエラーが多発して利用できない。
さらに、src/index.html
に以下のような変更を行なった。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Ionic App</title>
<base href="/" />
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<link rel="icon" type="image/png" href="assets/icon/favicon.png" />
<!-- add to homescreen for ios -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<!-- ここから追加 -->
<script>
if (global === undefined) {
var global = window;
}
</script>
<!-- ここまで追加 -->
</head>
<body>
<app-root></app-root>
</body>
</html>
src/app/app.module.ts
に以下のような変更を行う。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { AmplifyAngularModule, AmplifyService } from 'aws-amplify-angular'; // この行を追加
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
AmplifyAngularModule, // この行を追加
],
providers: [
StatusBar,
SplashScreen,
AmplifyService, // この行を追加
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule {}
src/main.ts
にAWS Amplifyの初期化処理を行うコードを追加する。
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
// ---------- ここから追加 ----------
import Amplify from 'aws-amplify';
import amplify from './aws-exports';
Amplify.configure(amplify);
// ---------- ここまで追加 ----------
// ついでにAmplifyのログレベルをDEBUGにしてわかりやすくする
(<any>window).LOG_LEVEL = 'DEBUG';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));
ここまで準備して$ ionic serve
コマンドを実行すると、Chromeのデベロッパーツールのコンソールに以下のようなログが出た。これで無事にAWS Amplifyの機能を初期化することができた。
$ ionic start
コマンドで自動的に生成されるHomePage
コンポーネントにAWS Amplifyで提供される<amplify-authenticator>
コンポーネントを配置してみる。
<ion-header>
<ion-toolbar>
<ion-title>
Ionic Blank
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<amplify-authenticator framework="Ionic"></amplify-authenticator>
</ion-content>
Ionic 4ではデフォルトで生成されるHomePageコンポーネントはHomePageModuleとしてメインのAppModuleから分けられているため、HomePageModuleでも必要なモジュールを読み込む必要がある。
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { HomePage } from './home.page';
import { AmplifyAngularModule, AmplifyIonicModule } from 'aws-amplify-angular'; // この行を追加
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
RouterModule.forChild([
{
path: '',
component: HomePage
}
]),
AmplifyIonicModule, // この行を追加
AmplifyAngularModule, // この行を追加
],
declarations: [HomePage]
})
export class HomePageModule {}
この状態で$ ionic serve
を実行すると以下のようなログインフォームが表示される。
これだけで、
の画面を一気に作ることができる。
明日はこれをベースとして、Ionic 4で複数の画面を追加する。AppModule以外のモジュールで毎度AmplifyIonicModuleやAmplifyAngularModuleを読み込まなくても済むように共通モジュールも作成したい。