今日こそやるぞ

本気を出せば今日できる

AWS Amplify / GraphQL Transform 紹介と雑感

tl;dr

AppSyncするならAmplify

突然ですが皆さん、AWSのAppSyncをご存知ですか?

AppSyncは、AWSRDBやDynamoDBなどをデータソースとし、そこから情報を取る処理も、新しく書き込む処理も、なんでもGraphQLリゾルバー経由で書けちゃうすごいやつです。API Gateway + Lambda構成でよくやるパターンをDSLでサクサク書けてしまうんですよね。

すばらしい、時代はGraphQLですね。

が、しかしAppSyncのドキュメントを見ると「AWSのWebコンソールから試してみよう!うわっ便利!うひょー!」というテンション高い話に終始していて、具体的にどうやって開発を進めたらいいのか、曖昧なままになっています。

「とりあえずDynamoDBとAppSyncを使ってみたけど、リポジトリで管理する方法がわからん」みたいな状態になりがちですよね。全員なりがちだと思うんですが、私は先週なりました。

そこを解決してくれるのがAWS Amplifyです。

そもそもはモバイル向け開発をやるためのフレームワークですが、その1機能としてAPI管理ができます。具体的には、AmplifyのなかでGraphQLスキーマを書くだけで、AWSの関連リソースがCloudFormation経由でデプロイされます。えっ何それすごくない…?

ということで、Amplifyを経由してAppSyncしましょう。

お試し

やっぱり公式ドキュメントを見るのが一番いいです。この辺ですな。

とりあえずWebアプリを作って配信するだけなら、create-react-appでも使いましょう。ただ今回、そこは本題でないので index.html 手書きでもいいです。

# Amplify CLIインストール
npm install -g @aws-amplify/cli
amplify configure

# 適当にアプリ作る
npx create-react-app my-app --typescript
cd my-app

# 配信できる!!!
amplify add hosting
amplify publish

# API生えた!!!
amplify add api
amplify push

なんと、上記のコマンドを叩いていけば、もうそれだけでS3から配信できてしまうのです1。いやこれ簡単ですね。

この調子で順次、コマンドから auth api analytics… と足していけば、それだけでWebアプリが作れちゃいます。SDKに必要な設定ファイルなども全部CLIが吐き出してくれます。これは快適です。

GraphQLスキーマのサンプルもついでに載せておきます。

type Blog @model {
  id: ID!
  name: String!
  posts: [Post] @connection(name: "BlogPosts")
}

type Post @model {
  id: ID!
  title: String!
  blog: Blog @connection(name: "BlogPosts")
  comments: [Comment] @connection(name: "PostComments")
}

type Comment @model {
  id: ID!
  content: String
  post: Post @connection(name: "PostComments")
}

こういう風に書くだけで、AppSyncで利用できる詳細なスキーマとリゾルバーが自動生成されちゃうのです。ストレージはDynamoDBにあって、モデル同士のリレーション処理とか、ユーザーベースの権限認証とかもそれなりにやってくれるんです。すごっ。

まだ使い込んでいない段階ですが、今のところFirebaseくんの生産性と殴り合えそうなアウラを感じます。アウラがすごいですよ、アウラが。

もっと真っ当な詳細を知りたい人はこちらのスライドを見ましょう。

speakerdeck.com

Amplify最高!

Amplifyコンソールもあるぞ

ややこしいんですが、名前が同じで微妙に関係ないAmplifyコンソールもあります。ドキュメントの紹介テキストが一番いいのでそれを読んで欲しいですが、

  • Amplify CLIでバックエンド書いてAmplifyコンソールに突っ込むと、つよいぞ
    • フロント・バックエンドをセットで、ブランチ単位の環境を作れるぞ
    • セットだからデプロイ工程が堅牢だぞ
  • ブランチ名サブドメインとか使えちゃうぞ
  • パスワード保護とかもできちゃうぞ

というような話が手短に書いてあります。つまり最高ってことなんですね。なお今月あたりから東京でも使えるようになっています。

aws.amazon.com

時代はAWS Amplify!!!

とまあそれで終わればよかったんですが

根本的なプロダクトの方向性として、とにかく「分かってる人」が楽をするツールとして作られているような節があり、このドキュメントを1から読むのは相当骨が折れるな… という気分です。実際に読んでみると、裏にあるのは普通のAWSサービス群ということもあって、既視感のある記述がほとんどではあるのですが。

例えばバックエンドのAPIを書こう、とりあえずデータ保存するくらいでいいから動くもの作ろう、となると、この辺を読むわけじゃないですか。

いやちょっと長いな… 長くない?

考えてみれば当然ですが、Amplifyを使ってバックエンドを書くということはつまり、DynamoDBとAppSyncとCognitoのドキュメントを全部読んでから出直してこいということなんですよね。そして本体のドキュメントを知っている前提なので、例えば.vtlという拡張子が何の説明もなく出てきます。この辺ちゃんと読んでなかったので無事死亡しました。

それに加えて、GraphQLスキーマから自動生成されるロジックにも今ひとつ馴染めないでいます。具体的には下記。

謎1. DynamoDBテーブルにプライマリソートキーがない

設定次第では無い、ということではなく、常に絶対にないようです。

まだまだDynamoDBに詳しくないのでアレですが、絶対にソートキーがないテーブルでは相当しんどいのでは…?

議論は進んでいるので、いずれ対応されるとは思いますが、現状では「手でテーブルを作るしかない」「既存のテーブルにつなげ」というような話になっています。

「デプロイが容易で確実」「環境構築が楽」という話とまったく噛み合わないので、ここはちょっと調べたいというか、救いがほしいというか…

謎2. アイテムのIDがUUID

デフォルトで生成されるロジックでは、DynamoDBに新しいアイテムを生成する際、mutation引数の1つとしてIDを渡すことができます。渡さないでおくとUUIDになります。

DynamoDB素人の的外れな感想かもしれませんが、私の理解する限り、プライマリパーティションキーを全部UUIDで埋められては使い物にならない気がします。

キーがUUIDになると、クライアント側としてはスキャンを要求するしかないはずです。実際に自動生成されたlist処理をみても、やはりスキャンをかけています。

#set( $limit = $util.defaultIfNull($context.args.limit, 10) )
{
  "version": "2017-02-28",
  "operation": "Scan",
  "filter":   #if( $context.args.filter )
$util.transform.toDynamoDBFilterExpression($ctx.args.filter)
  #else
null
  #end,
  "limit": $limit,
  "nextToken":   #if( $context.args.nextToken )
"$context.args.nextToken"
  #else
null
  #end
}

早すぎる最適化サムライになっても仕方ないので、デフォルト実装がスキャンでも全く問題ないかなとは思うのですが。しかし、これを後からベストプラクティスにしたがってクエリに変えようと思うと、それは中々しんどそうですよね。IDの設計からやり直しになるわけですし…

アイテム生成時にIDを指定してやればよい、とはいえ、どうせ手で書くならサーバー側にまとめて記述したいです。ただそうすると自動生成コードのコピペ修正が必須になってしまい、自動生成による旨味が激減しそう。

基本的なロジックが自動生成されるのは本当に嬉しいんですが、それをそのまんま使っていいのか悩ましい(素人にはわからない)ので、ちょっと戸惑いがありますですね。

謎3. ユーザー名で権限管理している (解決)

説明しずらいのですが、要するにこうです。

f:id:stakme:20190423184242p:plain

owner という属性にユーザー名が保存されています。この文字列を使って権限管理をしよう、ということです。

最初にこのデータを見たとき、「ユーザー名は変わるかもしれないのだから、文字列として保存しておくと壊れるのでは?」と心配になりました。なりますよね?

ところが実際のところは、Cognito User Poolに登録されたusernameは変更できないという仕様があります。ですから、文字列として保存しておいて問題ありません2

一事が万事この調子なので、AWSの細かい挙動を「分かっている」人でないと五里霧中という感じが強いです。

謎4. 認証の世界観が謎

Amplifyでは、認証周りをシンプルに扱えるReactコンポーネントが提供されています。TypeScript定義がまだないようですが、それは大した問題ではないので置きます。

使うときはこんな感じです。

import { withAuthenticator } from "aws-amplify-react";
import App from "./App.tsx";
export default withAuthenticator(App)

これで「ログインしていないと常にログインメニューだけが表示されるアプリ」が完成します。この簡単さには目を見張るものがあります。

ただ、業務用のアプリなら「このサイトはログイン強制です」の一言で済むと思いますが、一般のWebアプリで「ログインしないと何も見えないサイト」というのはむしろレアケースです。ログインしないユーザーを想定しているからこそ、Cognito IDプールもUnauthロールとか何とか用意してくれているわけです。

そういった「普通のサイト」をどう作るんでしたっけ、という部分について、実はまだ全く理解できていません。ドキュメントを読みつつ手を動かしてみようという段階です。

AppSyncのエンドポイントを叩くためにはCognito認証を通す必要があるはずなので、なんかあるんだろうとは思います。たぶんね。

とりあえずAmplify推していくぞ

現時点でAWS Amplifyの印象としては面白そうだが、良くも悪くもAWSであり、とりあえず独自ドキュメントが長いといったところです。

ドキュメントくらい読めよ!という話は本当にそうなんですが、実際のところ人間はREADMEを読まないものなので、この取っ付きにくさで流行るのか心配だなーというところがあります。

他方、GraphQLスキーマをいじるとAppSyncにエンドポイントが生え、DynamoDBが用意され、IAMもよしなに処理される(今回は触れていませんが画像用S3なども利用できる)という仕組み全体はかなり面白いですし、もちろん実用的でもあります。背後にいるのが普通のAWSサービス群なので、最悪そこに手を突っ込めばなんとかなるだろうという思惑 / 安心感も働きます。

全体としてみると、まあこれは流行るだろうな… というお気持ち表明記事でした。


  1. CloudFrontも噛ませられる。実際にはCLIが「開発用ならS3 + httpでいいんじゃないかと思うが?」などと提案してくれる。親切最高〜

  2. どうしてもユーザー名を保存したくなければ @auth(identityField: "sub")