# はじめに
最近家でNatureRemoを購入したのでAPI経由でデータを保存して、可視化までをプライベートなプロジェクトとして始めております。せっかくなので技術的挑戦を第一にSAMを利用してデータを登録、取得するAPIを作成することを第一目標に始めました。
今回行うのはSAMでNature Remoのデバイス情報を取得してDynamoDBへデータをPUTし、データをGETします。
# SAMとは
AWS Serverless Application Modelの略でLambda, API Gateway, DynamoDBのリソースを一元管理することができるAWSのツールです。
SAMを開発するためにローカル環境構築を行います。AWS が提供しているSAMのローカル環境用のSAM Localと公式のDocker ImageからDynamoDBを連携しSAMコマンドで雛形のプロジェクトを作成することができます。今回はRuntimeをGoで作成していきました。
SAMとDynamoDBは実際に触ったことがなかったので理解にかなり時間がかかりました。。
# Prerequisite
・python
・aws-cli
・aws-sam-cli
・Nature Remo API
・Docker
・dynamo-local
# 作成コード
# 仕様
GET・・・ DynamoDBに保存しているデータからHashでデータを検索して一覧情報をjsonで表示する
POST・・・NatureRemo APIからイベントが起こった時刻のデバイス情報を取得してDynamoDBにデータを登録する
#1 ローカル環境作成
まずはじめにローカル環境構築から始めます。
※ Prerequisiteのコマンドは事前にインストール・設定してください。
samコマンドが入っている状態でsamの雛形プロジェクトを作成します、runtimeのオプションで利用言語を選択することができます(Golang, Node, Python)
今回はGolangで開発していきました。
$ sam --version
SAM CLI, version 0.18.0$ sam init --runtime go --name プロジェクト名
HelloWorldを出力するSAMのテンプレートプロジェクトが作成されます。
#2 DynamoDBをDockerで立ち上げる
docker-compose.ymlにSAMのlambdaに紐付けるDynamo localを立ち上げる
version: '3'
services:
dynamodb:
image: amazon/dynamodb-local
container_name: dynamodb
ports:
- 18000:8000
かけたらDocker コンテナを立ち上げていきます。
$ docker-compose up
立ち上がることが確認できたらテーブルと擬似データを入れてみます。
テーブル名はRemo、Hash(Primary key)にType、Range(Sort key)にDateTime、値にValueの3つの項目を用意します。
ddlフォルダを作成し、テーブル定義と擬似データのそれぞれのファイルを以下のように作成します。
{
"AttributeDefinitions": [
{
"AttributeName": "Type",
"AttributeType": "S"
},
{
"AttributeName": "DateTime",
"AttributeType": "S"
}
],
"TableName": "Remo",
"KeySchema": [
{
"AttributeName": "Type",
"KeyType": "HASH"
},
{
"AttributeName": "DateTime",
"KeyType": "RANGE"
}
],
"ProvisionedThroughput": {
"ReadCapacityUnits": 2,
"WriteCapacityUnits": 2
}
}
テーブルが上記のように定義ができたらawsコマンドでローカルのDynamoDBにテーブルを反映させます。
$ aws dynamodb create-table --cli-input-json file://ddl/create_remo_table.json --endpoint-url http://localhost:18000
作成ができれば擬似データを登録していきます。
{
"Remo": [
{
"PutRequest": {
"Item": {
"Type": { "S": "hu" },
"DateTime": { "S": "1565882716" },
"Value": { "S": "60.1" }
}
}
},
{
"PutRequest": {
"Item": {
"Type": { "S": "hu" },
"DateTime": { "S": "1565913775" },
"Value": { "S": "40.5" }
}
}
},
{
"PutRequest": {
"Item": {
"Type": { "S": "mo" },
"DateTime": { "S": "1565740975" },
"Value": { "S": "1" }
}
}
}
]
}
上記の擬似データをawsコマンドでローカルのDynamoDBへ登録します。
$ aws dynamodb batch-write-item --request-items file://ddl/put_remo_sample_date.json --endpoint-url http://localhost:18000
※テーブルを削除する場合はdelete-tableオプションを使います。
$ aws dynamodb delete-table --endpoint-url http://localhost:18000 --table-name Remo
#3 Dynamo LocalとSAMを連携し、データを取得する
これまでDynamoDBにデータを登録することができたのでSAMからデータを取得するまでやってみます。
今回利用するGoのライブラリは以下の通りです。
・aws-sdk-go ・・・AWS公式
・aws-lambda-go・・・AWS公式
・guregu/dynamo ・・・DynamoDBをより扱いやすくしたaws SDKのラッパー
・tenntenn/natureremo・・・Nature Remo用のライブラリ
template.ymlにてPUTとGETの関数を作成し、sam localのAPIを起動します。
SAMのテンプレートはCloudformationのテンプレートの拡張です。
今回は以下のように記載しました。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
nature-remo-sam-dynamo
Sample SAM Template for nature-remo-sam-dynamo
Globals:
Function:
Timeout: 5
Resources:
RemoFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: nature-remo/
Handler: nature-remo
Runtime: go1.x
Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html
Policies: AmazonDynamoDBFullAccess
Events:
GetNatureRemo:
Type: Api
Properties:
Path: /nature-remo/{Type}
Method: GET
PutNatureRemo:
Type: Api
Properties:
Path: /nature-remo
Method: POST
Environment:
Variables:
DYNAMODB_ENDPOINT: ""
DYNAMODB_TABLE_NAME: !Ref RemoDynamoDBTable
NATURE_REMO_ACCESS_TOKEN: ""
RemoDynamoDBTable:
Type: AWS::Serverless::SimpleTable # DyanmoDB resource
Properties:
PrimaryKey:
Name: Type
Type: String
ProvisionedThroughput:
ReadCapacityUnits: 2
WriteCapacityUnits: 2
Outputs:
NatureRemoAPI:
Description: "API Gateway endpoint URL for Prod environment for First Function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/nature-remo/"
RemoFunction:
Description: "First Lambda Function ARN"
Value: !GetAtt RemoFunction.Arn
RemoFunctionIamRole:
Description: "Implicit IAM Role created for Remo function"
Value: !GetAtt RemoFunctionRole.Arn
それではsamを起動させます。事前に環境変数情報を設定して立ち上げる場合以下のコマンドのオプション — env-vars
つけ、対象ファイルenv.jsonにTable情報やToken情報を設定し、起動させます。
$ sam local start-api --env-vars ./env.json
127.0.0.1:3000
でデフォルトで立ち上がるためtemplate.ymlのpath(nature-remo/{Type})でURLにアクセスする。
Typeに入るのはNatureRemoのデバイス情報(hu, li, mo, te)です。
Typeをhuでアクセスすると以下のようにhuで保存された情報がjsonで返却されることが確認できます。データがない場合はnullを返却します。
[
{
"type": "hu",
"datetime": "1566224912",
"value": "60"
}
]
#4 Nature Remo API Tokenを取得し、Goでデバイス情報にアクセスする
以下URLからNatureRemoAPIのアクセストークンを発行する。
発行が確認できたらcurlでAPIデータが取得できるか確認する。
API自体はNatureRemo公式がSwaggerにてドキュメントにまとめてくれているためその中のデバイス情報APIをcurlで叩く。
$ curl -X GET "https://api.nature.global/1/devices" -H "accept: application/json" -k --header "Authorization: Bearer 取得済みToken"
jsonでデバイス情報が返却されることが確認できればTokenが有効でAPIにリクエストを送ることができていることがわかります。
#5 SAMでデバイス情報をDynamoDBに保存する
それではGoでNatureRemoのデバイス情報を取得し、DynamoDBに保存します。
書いたコードは完成作品を見れば分かるのですが、ポイントとしてはデータ登録時にtransactionの対応を公式のaws-sdkの関数で対応しています。
#6 AWSコマンドでAWS上にstackを作りローカルと同じ構成を構築する
template.yamlの記述が正しいかをsam validateで確認する。
$ sam validate
確認ができればtemplate.yamlを配置するS3を作成する。
※必要があれば — profile [プロファイル]を追加する
$ aws s3 mb s3://nature-remo-sam-dynamo --region ap-noarth
S3にtemplate.yamlをパッケージ化してS3に配置する、パッケージ化したcloudformationファイルはpackage.yamlとしてローカルコードに吐き出す。
$ sam package --template-file template.yaml --s3-bucket nature-remo-app --output-template-file package.yaml
stackをAWSにデプロイし、サービスが展開されているか確認する。
$ sam deploy --template-file package.yaml --stack-name nature-remo-sam-dynamo --capabilities CAPABILITY_IAM --region ap-northeast-1
SAMでの開発を行う中でハマったポイントとしてはローカル開発時にDockerで立ち上げたDynamoDBのコンテナとMac OS側のhostが連携できない(エンドポイントがうまく合わない)ことにハマりましたがそちらは http://host.docker.internal:18000
で解消しました。
開発時にNature Remo APIに5分間で30リクエスト以上を投げてしまったためしばらく(1日ぐらい)リクエストが制限されてしまった。
# まとめ
SAMを使ったAPI開発をしていきました。SAM自体をあまり知らなかったためなかなかスムーズに進みませんでしたがなんとか構築することができました。
次回はScheduleでデータを定期的に溜めた後に可視化するための表示画面を作っていけたらと思います。
最後まで読んでいただきありがとうございます。