この記事はMonstar Lab, Inc. Advent Calendar 2016 22日目の記事です。
3ヶ月ほど前になりますが ServerlessConf Tokyo に行って、Serverlessというバズワードに取り組みたい気持ちが高まりました。サーバレスは銀の弾丸ではなく使える場所も限定的という意見もありましたが、CloudGuru の発表では多くの部分を積極的にサーバレスで構築している例が出るなど、サーバレスの可能性も感じました。
また先日行ったIVS CTO Night 2016 Winterで 事例にこだわりすぎる。最新技術に事例があるはずがない。まずはやってみよう、少なくとも試そう。 という話に感銘を受けました。
AWSのサーバレススタックによくある典型例ですが、APIGateway + Lambda + DynamoDB という構成をServerlessのフレームワークを使ってやってみました。 Serverless フレームワークは以下のようにさまざま出てきていますが、今回はServerless Frameworkを使いました。
Serverless Framework とは
Serverless Frameworkを使うと、YAMLに設定を書いてCLIのコマンドを実行するだけで、AWS設定やデプロイ、コードの呼び出しが簡単に行えます。手作業の多くを自動化してくれるし、GitHubでの管理やCI連携も楽になります。今回は Serverless FrameworkのexampleであるServerless REST APIをベースに変更を加えます。
アーキテクチャ
Serverless Frameworkも使いながら作ったプロトタイプです。
スクリーンキャプチャ S3にアップロードされた画像を一覧取得し表示する
プロトタイプのキャプチャです。アーキテクチャの6, 7, 8の部分になります。
スワイプするとAPIで取得した画像一覧の画像を時系列で表示します。ジミヘンに深い理由はなく別のプロトタイプで使った画像が手元にあったからです。
GitHubリポジトリ
serverless-rest-api-with-dynamodb
デプロイ
詳細はリファレンスを参照するとして、以下のコマンドでAWSにデプロイするとYAMLで定義したAPIエンドポイントができます。
$ sls deploy -v
endpoints:
POST - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/shareImages
GET - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/shareImages
GET - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/shareImages/{id}
PUT - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/shareImages/{id}
DELETE - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/shareImages/{id}
DynamoDBのテーブル設計について
DynamoDBのテーブル設計は奥が深いです。パーティションキーやソートキー、ローカルセカンダリインデックスやグローバルセカンダリインデックスをどのように設計するかがポイントになります。この動画やStackoverflowを見ると理解が進みました。
- Amazon DynamoDB テーブル設計と実践 Tips
- How to query DynamoDB by date (range key), with no obvious hash key?
- Querying DynamoDB by date
ところが、DynamoDBテーブルのServerless.ymlでの記述方法がネットには少ない状況です。思ったようなテーブル設計をYAMLに記述するのには苦労するかもしれません。以下はグローバルセカンダリインデックスを貼った例で、この記事の一番の価値かもかもしれません。
resources:
Resources:
ShareImagesDynamoDbTable:
Type: 'AWS::DynamoDB::Table'
DeletionPolicy: Retain
Properties:
AttributeDefinitions:
-
AttributeName: id
AttributeType: S
-
AttributeName: createdAt
AttributeType: N
-
AttributeName: createdMonth
AttributeType: S
KeySchema:
-
AttributeName: id
KeyType: HASH
-
AttributeName: createdAt
KeyType: RANGE
GlobalSecondaryIndexes:
-
IndexName: globalIndex1
KeySchema:
-
AttributeName: createdMonth
KeyType: HASH
-
AttributeName: createdAt
KeyType: RANGE
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: 'shareImages'
簡単に説明すると、
- アップロードした画像id ハッシュのパーティションキー
- 作成日時 createdAt レンジのソートキー
- プライマリキー以外の属性は、定義しないでも良い。たとえばtitleやimageUrlなど
- アイテムの更新や削除はidを指定してRESTfulに行う
- ただし、このプライマリキーではquery関数でタイムラインのような時系列での画像一覧の取得ができない
- よって、作成年月(例:201612) createdMonth をグローバルセカンダリインデックスとして作成する。日付や時間ベースでのパーティション分けもありです。
- DynamoDBの性質にパーティションキーでのデータの分散配置があるので、できるだけ異なるハッシュ値で分散させた方がパフォーマンス上、良いはずです。
iOSクライアントについて
iOSクライアントについては、主旨がズレるので詳細を割愛しますが、以下のようなものです。
- Facebook SDK によるログイン
- AWS SDK によるCognito認証、S3への画像アップロード
- 画像ダウンロード kingfisher
- APIクライアントは自作APIKit風インタフェースの Alamofire + ObjectMapper + RxSwift。 APIKitを使わず自作するのは、自作の方が細かい部分のコントロールが効きやすいからです。また作ってもそれほど多くのコード量になりません。
Serverless アンチパターン
サーバレスにはアンチパターンもあります。
- 全てをサーバレスにする必要はない。サーバレスにすることが目的ではない。
- 単純にサーバの代わりとは考えない。従来の箱としてのサーバとは別物。同じように扱おうとする失敗する。
- コストへの過剰な期待はやめる。コスト効率がいいのは事実だが、絶対的に安いというわけではない。コストだけに着目するべきではない。
- API Gateway + Lambdaに囚われすぎる。 KinesisではなくAPI Gatewayが必要か?
- Microservicesと混同しすぎる。あるいは夢見すぎ。自然とMicroservicesに近づきやすいが、Serverless != Microservice
次にやってみること
- APIGatewayをCognitoでアクセスコントロールする。APIClientは自前のものを使いたい。
- 開発スピードをあげるためにDynamoDBをローカルで動かしたい。このプラグインで出来るかな?
- AWS Step Functions で Lambda を繋いでみる。
とても手軽にAPIが出来たので、気分も盛り上がりました。 まずはやってみよう、少なくとも試そう。 の精神でサーバレスの動向を追っていきたいです。
次は23日目の @omatsu さんです。楽しみですね。