この記事はServerless Advent Calendar 2017の21日目の記事です。
AppSyncのプレビュー申請が続々と通っているようです。
私も発表直後に申請をしていて、これを書く日までに申請が通っていればAppSyncを使った何かをやってみようかと考えていたのですが、まだ通らないのでやけになってこんなことやってみました。
ぐぐってみるとNode.jsでのサーバーレスGraphQL実装事例はよく出てくるのですがPythonの事例は見つけられなかったので、やってみました。
なおGraphQLの技術については勉強中でまだあやふやなので説明はしないです。ここでは、わーい動いたー!までにとどめます。
やりたいこと
クライアント
↓ ↑(GraphQL)
API Gateway
↓ ↑
Lambda(Python3)
↓ ↑
DynamoDB
環境
$ python --version
Python 3.6.1
$ pip --version
pip 9.0.1
使用するフレームワーク・モジュール
Graphene
GraphQLのフレームワーク。
PynamoDB
DynamoDBをPythonからシンプルに使えるようにしてくれるやつ。
graphql-pynamodb
flask + Graphene + PynamoDBをインテグレーションしてくれる。
Zappa
サーバーレスのフレームワーク。Flaskで書いたアプリケーションをサーバーレスアプリケーションとしてデプロイできる。API GatewayやLambdaをデプロイしてくれる。
使用するサンプル
graphql-pynamodbにはFlaskアプリケーションのサンプルが含まれていて、これを使うとDynamoDBのデータをGraphQLでクエリできるFlaskアプリケーションが構築できます。(少し手直しが必要だったので後で書きます。)
サーバーレスのフレームワークとしてZappaを選択した理由はそのためです。このFlaskアプリをそのままZappaを使ってデプロイすればサーバーレス化できるでしょという単純な理由です。
準備
$ git clone https://github.com/yfilali/graphql-pynamodb.git
$ cd graphql-pynamodb/examples/flask_pynamodb
$ virtualenv env
$ source env/bin/activate
$ pip install -r requirements.txt
ここまではREADMEの手順通り。
requirements.txt
に書いてあるモジュールのバージョンが古く、エラーが出たので最新版をインストールします。
$ pip install graphene-pynamodb==1.0.0 graphene==2.0
graphene
とgraphene-pynamodb
のバージョンを変えました。
次にmodels.py
を修正します。
ここはDynamoDBのキー名やデータの型などを定義します。このサンプルではローカルのDBを見に行っているようなのでコメントアウトし、リージョンを指定します。すべてのクラス内で以下のように修正します。
# host = "http://localhost:8000"
region = 'ap-northeast-1'
次にdatabase.py
を修正します。
これはapp.py
でアプリケーションを起動させたときにデータベースを初期化するためのスクリプトが書いてあるのですが、うまくDynamoDBが作成できなかったり、起動するたびにデータが追加されてしまうなど都合が悪かったので修正しました。
from uuid import uuid4
def init_db():
# import all modules here that might define models so that
# they will be registered properly on the metadata. Otherwise
# you will have to import them first before calling init_db()
from models import Department, Employee, Role
if not Department.exists():
Department.create_table(read_capacity_units=1, write_capacity_units=1, wait=True)
engineering = Department(id=str(uuid4()), name='Engineering')
engineering.save()
hr = Department(id=str(uuid4()), name='Human Resources')
hr.save()
if not Role.exists():
Role.create_table(read_capacity_units=1, write_capacity_units=1, wait=True)
manager = Role(id=str(uuid4()), name='manager')
manager.save()
engineer = Role(id=str(uuid4()), name='engineer')
engineer.save()
if not Employee.exists():
Employee.create_table(read_capacity_units=1, write_capacity_units=1, wait=True)
peter = Employee(id=str(uuid4()), name='Peter', department=engineering, role=engineer)
peter.save()
roy = Employee(id=str(uuid4()), name='Roy', department=engineering, role=engineer)
roy.save()
tracy = Employee(id=str(uuid4()), name='Tracy', department=hr, role=manager)
tracy.save()
これでアプリケーション起動時にDynamoDBが存在しなければ作成し、サンプルデータが入るようになりました。
schema.py
とapp.py
はそのままでOKです。
ローカルで実行
まずはローカルで動くかテストしてみます。
$ python app.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 287-543-857
初回起動時にDynamoDBテーブルを作る処理が入るので起動まで少し時間がかかります。
起動したらhttp://127.0.0.1:5000/graphqlにアクセスしてみます。そうするとGraphiQLというGUIでクエリ操作できるコンソールにアクセスできます。
そこでこのようにクエリをリクエストしてみます。
{
allEmployees {
edges {
node {
id,
name,
department {
id,
name
},
role {
id,
name
}
}
}
}
}
DynamoDB内のすべてのデータが取得できました。試しにid
とかname
とかを消してみるとクエリ文に入っているものだけが取得できると思います。
サーバーレス化
今はローカルでFlaskサーバが動いているので、これをZappaでデプロイしてサーバーレス化してみます。
$ pip install zappa
$ zappa init
色々聞かれますがcredentials profileにdefaultを使うのであれば全部そのままエンターで問題ないかと思います。
デプロイ
zappa_settings.json
ができていることを確認したらデプロイします。
$ zappa deploy
・・・
Deployment complete!: https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev
出力されたエンドポイント+/graphql
がGraphQLエンドポイントとなります。
確認
確認するためにChromeのエクステンションなどからクエリしてみます。
私はchromeiqlを使用しています。Chromeにダウンロードして開くと、「Set endpoint」のテキストボックスが表示されるのでそこにGraphQLのエンドポイントを入力します。
エンドポイントはこんな感じ。
https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/graphql
Set endpointすると先ほどと同様、GraphiQLのコンソール画面が表示されます。
左側に先ほどローカルで確認したときのクエリを入れて実行してみると、同様の結果が返ってくるかと思います。
注意点
ローカルでテストしたときはFlask起動時の処理としてDynamoDBテーブルがない場合は作成をしていましたが、サーバーレス化したあとはその処理はしてくれないので、テーブルがない場合は自分で作る必要があります。今回はローカルで作ったやつをサーバーレスアプリからも使いまわしているので問題ないかと思います。
リソース削除は$ zappa undeploy
。
まとめ
Flaskで動いていたものを手直しせずにデプロイしてサーバーレス化できちゃうZappaすごい。
AppSync〜〜〜〜〜!!!