AWS+Serverless+DynamoDB+APIGateway+Lambdaでアプリ用サーバーAPIを簡単？に作成する

やります。

今回こちらのアプリで件名の感じで、あれをこうしました。
[ボールをゴールへドーン ios](https://itunes.apple.com/jp/app/da-renmohamaru-jian-dan-naotore/id1033716298?mt=8)
[ボールをゴールへドーン Android](https://play.google.com/store/apps/details?id=jp.mokosoft.ball&hl=ja)
ゲームはCocos2d-xで開発をしています。
ゲーム内で、ユーザー問題作成機能的なものを提供してそちらで使っています。


まずServerlessを使えるようにしないと話になりません。

Serverlessの導入はこちらを参考にしてみてはいかがでしょうか。
[とことんサーバーレス①：Serverless Framework入門編](http://qiita.com/hiroshik1985/items/6d979ff1afb56953b62e)

こちらも超参考にさせていただきました
[Serverless Framework で DynamoDB を使う](http://qiita.com/katsuhiko/items/a2594e73108728a22410)


Serverlessの導入ができたら、まずは単純なAPIを作成して、deployしてみましょう。


```bash:command-line
$ sls create --template aws-nodejs --name test
$ sls deploy -v
```

自分はnodejsを使うことにしましたが、pythonとかC#とか使えちゃうらしいですね。


ベロベロとなんか勝手につくられます。
<img width="384" alt="スクリーンショット 2017-01-25 21.49.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/81327/d59fefee-9728-7222-ccf4-3e80030508db.png">


最小の段階だと、lambdaが１個作られるだけなので、AWSの管理画面から確認する。
<img width="455" alt="スクリーンショット 2017-01-25 21.48.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/81327/444ba203-dad1-27d6-008b-c3bc2d2c1af4.png">

なんかできている。
あとはこれに付けたし付け足ししていこう。

まず、nodejsのパッケージを色々使うので、npmのセットアップをしたいと思う（意味わかってない）

```bash:command-line
$ npm init
(なんかいろいろでるので、いろいろする)
```

すると、package.jsonというファイルが作られる。
<img width="595" alt="スクリーンショット 2017-01-25 22.08.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/81327/a78934fa-ee93-c0a0-c9c1-9cee87ad0ae2.png">

これに
<img width="310" alt="スクリーンショット 2017-01-25 22.17.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/81327/685704dc-4389-28ef-6658-bb2ee7c16d18.png">

こんな感じで dependenciesを追加します。
意味？しるか。

```bash:command-line
$ npm install
```
とかやると、npmが関連ライブラリを調べてインストールしてくれます。
これはローカルに展開されるんですね。
最終的にServerlessでdeployしたときに、諸々の関連ファイルを全部zipで固めてアップしてくれる段取りになっているみたいです。



次にserverless.yamlを変更します。
ここに定義を書いていって、最終的にはCloudFormationとかいう機能を使うみたいですね。
それが、まとめたサーバー構成のセットアップ手順を定義する仕組みで、
簡単に構成を再構築することができるやつみたいな感じっていうかんじー？


```yaml:serverless.yaml

....

provider:
  name: aws
  runtime: nodejs4.3
  iamRoleStatements: # ロールを新たに追加します
    - Effect: Allow
      Action:
        - dynamodb:DescribeTable
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: "arn:aws:dynamodb:us-east-1:*:table/dev-test-*" #us-east-1リージョンの dev-test-なんたらってテーブル全部に権限あたえますよって感じ


functions:
  hello:
    handler: handler.hello
  userCreate: #これがlambdaコマンドになる
    handler: handler.userCreate #handler.js のどのメソッドを使うか
    events: #lambdaを何経由で起動させるか
      - http: # API Gatewayを使う
          path: user # https://awsのどっか.../user  というURLになる
          method: post #意味わかるやろ
          cors: true
  userRead: #こいつは複数件よむやつ
      handler: handler.userRead
      events:
        - http:
            path: user
            method: get
            cors: true
  userGet: #こいつは１件だけ読むやつ
      handler: handler.userGet
      events:
        - http:
            path: user/{id}
            method: get
            cors: true


resources: #必要なリソースを用意するよ！
  Resources: #重要なので２回書きました
    UserDbTable: #リソース識別名です。使われません
      Type: 'AWS::DynamoDB::Table' #DynamoDB テーブルをつくるよ
      DeletionPolicy: "Delete" #テーブルを容赦なく削除します。しない設定もある。調べろ
      Properties: #細かい設定やで
        AttributeDefinitions: #最低限必要となるカラム的なやつ
          -
            AttributeName: "id" #IDカラム
            AttributeType: "S" #文字列型
          -
            AttributeName: "delfg" #削除フラグ、使わないけど使う
            AttributeType: "N" #
          -
            AttributeName: "updt" #更新日時とかいれる、後にIndex使うためのカラム
            AttributeType: "S" #
        KeySchema: #主キー的なやつだよ
          -
            AttributeName: "id" #
            KeyType: "HASH" #HASH形式にする 他何があったか忘れた
        ProvisionedThroughput: #これがプロヴィジョニングのキャパシティだ！
          ReadCapacityUnits: "1" #読み込みキャパ
          WriteCapacityUnits: "1" #書込キャパ　とりあえず１で後で管理画面から変えれる
        GlobalSecondaryIndexes: #Index これがないと副次ソートできないの(TдT)
          -
            IndexName: "myUPDT" #なまえ
            KeySchema:
              -
                AttributeName: "delfg" #これは固定値をいれるため
                KeyType: "HASH" #
              -
                AttributeName: "updt" #並び替えをしたいカラム
                KeyType: "RANGE" #
            Projection: #投影するカラム（一緒に抽出したいカラム）
              NonKeyAttributes:
                - "id" #
                - "username" #
              ProjectionType: "INCLUDE"
            ProvisionedThroughput:
              ReadCapacityUnits: "1" #キャパだよー
              WriteCapacityUnits: "1"
        TableName: "dev-test-users" #作成されるDynamoDBテーブル名

```

こんな感じですかね。
適宜コメントで注釈いれてますが、詳しくはCloudFormationのヘルプに詳しく書いてあります。

Tableを増やしたいときは、YAMLの記述にのっとって、
Resourcesの後ろにバンバン追加していくんですね。
S3のBucketとかもつくれます。


ここまでで、AWSに、lambda APIGateway DynamoDBを作って、つなげるところまでやってくれます。
あとはhandler.jsの中に、処理を書くだけです。

```js:handler.js
'use strict';

const AWS = require('aws-sdk'),
      dynamoDb = new AWS.DynamoDB.DocumentClient(),
      uuid = require('uuid'),
      moment = require('moment'),
      userTableName = `dev-test-users`;

const createResponse = (statusCode, body) => (
  {
    statusCode,
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    body: JSON.stringify(body),
  }
);

module.exports.hello = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Go Serverless v1.0! Your function executed successfully!',
      input: event,
    }),
  };

  callback(null, response);

  // Use this code if you don't use the http event with the LAMBDA-PROXY integration
  // callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event });
};

module.exports.userCreate = (event, context, callback) => {

  const item = JSON.parse(event.body);
  item.id = uuid.v1();
  item.updt = moment().utc().toISOString();
  item.delfg = 0;

  const params = {
    TableName: userTableName,
    Item: item
  };

  dynamoDb.put(params, (err, data)=>{
    if(err){
      callback(null, createResponse(500, {Message: err.message}));
    } else {
      callback(null, createResponse(200, {id: item.id}));
    }
  });

};

module.exports.userRead = (event, context, callback) => {

  let page = "";
  let limit = 4;
  let my_index = "myUPDT";
  if(event.queryStringParameters != null)
  {
    if(event.queryStringParameters.page !== undefined ) {
      page = event.queryStringParameters.page;
    }
    if(event.queryStringParameters.limit !== undefined ) {
      limit = event.queryStringParameters.limit;
    }
  }

  const params = {
    TableName: userTableName,
    IndexName: my_index,
    KeyConditionExpression: "#key1 = :val1",
    ExpressionAttributeNames: { "#key1": "delfg" },
    ExpressionAttributeValues: {":val1": 0 },
    ScanIndexForward: false,
    Limit: limit
  };

  if(page != "" ) {
    let js = JSON.parse(page);
    params["ExclusiveStartKey"] = js;
  }

  dynamoDb.query(params, (err,data) =>{
    if(err){
      callback(null, createResponse(500, { message: err.message }));
    } else {
      callback(null, createResponse(200, data));
    }
  });



};

module.exports.userGet = (event, context, callback) => {

  const id = event.pathParameters.id;
  
  const params = {
    TableName: userTableName,
    Key: {
      id: id
    }
  };

  dynamoDb.get(params, (err,data) =>{
    if(err){
      callback(null, createResponse(500, { message: err.message }));
    } else {
      callback(null, createResponse(200, data));
    }
  });

};


```

たぶんこんな感じ。

これで、もっかい


```bash:command-line
$ sls deploy -v
```

deployしますと
べろべろーっつって
<img width="855" alt="スクリーンショット 2017-01-26 0.18.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/81327/d9ada176-0b34-dc71-7ae0-de0f69d9ae75.png">

ここにEndpointつくりましたよって結果がでてきます。
あとはこのAPIを使って通信すればOKです。

実際にAWS管理画面でみてみると

dynamoDB
<img width="486" alt="スクリーンショット 2017-01-26 0.22.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/81327/a1ada9af-ae26-1699-93ee-6dc4e04a7355.png">

API Gateway
<img width="296" alt="スクリーンショット 2017-01-26 0.22.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/81327/ba9a1283-290f-6943-e544-f194ed5c4838.png">

Lambda
<img width="446" alt="スクリーンショット 2017-01-26 0.22.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/81327/e76a06ab-661b-f0b7-6925-d96d187230d5.png">


それぞれ作成されているのがわかります。



それでは、試しに、作成するjsonをなげてみます。

```bash:command-line
curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"takaoka"}' https://h86ribf3tj.execute-api.us-east-1.amazonaws.com/dev/user
```

これで、username=takaokaというデータをつくってくれるはずです。

<img width="645" alt="スクリーンショット 2017-01-26 0.34.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/81327/6afe3a4c-f43c-41fe-2c15-a6aaaa8078a4.png">

dynamoDBでデータをみてみると、作られているのがわかります。
ここでは他のデータも投入した結果が表示されていますね。


ではデータ取得はどうでしょうか。

```bash:command-line
curl https://h86ribf3tj.execute-api.us-east-1.amazonaws.com/dev/user
```

とすると

```bash:command-line
{
  "Items":[
  {
    "updt":"2017-01-25T15:35:43.408Z",
    "username":"merukuma",
    "id":"eef18bf0-e313-11e6-b508-c1acf2e4eaa5",
    "delfg":0
  },
  {
    "updt":"2017-01-25T15:35:16.039Z",
    "username":"takaoka",
    "id":"dea18570-e313-11e6-ba2a-9d6ebdcb394f",
    "delfg":0
  }
  ],
  "Count":2,
  "ScannedCount":2
}
```

このJSONが返ってきます。

個別取得のAPIは

```bash:command-line
curl https://h86ribf3tj.execute-api.us-east-1.amazonaws.com/dev/user/eef18bf0-e313-11e6-b508-c1acf2e4eaa5
```

発行されたIDを使います。

```bash:command-line
{
  "Item":{
    "updt":"2017-01-25T15:35:43.408Z",
    "username":"merukuma",
    "id":"eef18bf0-e313-11e6-b508-c1acf2e4eaa5",
    "delfg":0
  }
}
```

とれてますね。
あとは、アプリから、http通信を行ってJSONのやりとりをするだけです。

そのうち、各プラットフォームでのクライアント通信の処理もかきたいですーしょりしょりー


Cocos2d-xでの通信処理


```cpp:nankatusin.cpp
#include "json11.hpp"
#include "network/HttpClient.h"



....


    cocos2d::network::HttpRequest *req = new cocos2d::network::HttpRequest();
    std::string query = "https://h86ribf3tj.execute-api.us-east-1.amazonaws.com/dev/user";

    req->setUrl(query.c_str());

    json11::Json::object obj;
    obj["user_name"] = user_name;
    std::string json_str = json11::Json(obj).dump();

    req->setRequestType(cocos2d::network::HttpRequest::Type::POST);
    std::string postdata = json_str;
    req->setRequestData(postdata.c_str(), postdata.length());

    req->setResponseCallback([=](cocos2d::network::HttpClient* sender, cocos2d::network::HttpResponse* respons){
        
        if ( respons->isSucceed() && respons->getResponseCode() == 200 ) {

# いろいろがんばってください

        }

    }

    cocos2d::network::HttpClient::getInstance()->send(req);


```



なんとなくわかるやろう。