1. Qiita
  2. 投稿
  3. serverless

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

  • 4
    いいね
  • 0
    コメント

やります。

今回こちらのアプリで件名の感じで、あれをこうしました。
ボールをゴールへドーン ios
ボールをゴールへドーン Android
ゲームはCocos2d-xで開発をしています。
ゲーム内で、ユーザー問題作成機能的なものを提供してそちらで使っています。

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

Serverlessの導入はこちらを参考にしてみてはいかがでしょうか。
とことんサーバーレス①:Serverless Framework入門編

こちらも超参考にさせていただきました
Serverless Framework で DynamoDB を使う

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

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

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

ベロベロとなんか勝手につくられます。
スクリーンショット 2017-01-25 21.49.59.png

最小の段階だと、lambdaが1個作られるだけなので、AWSの管理画面から確認する。
スクリーンショット 2017-01-25 21.48.17.png

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

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

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

すると、package.jsonというファイルが作られる。
スクリーンショット 2017-01-25 22.08.21.png

これに
スクリーンショット 2017-01-25 22.17.01.png

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

command-line
$ npm install

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

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

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: #こいつは1件だけ読むやつ
      handler: handler.userGet
      events:
        - http:
            path: user/{id}
            method: get
            cors: true


resources: #必要なリソースを用意するよ!
  Resources: #重要なので2回書きました
    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" #書込キャパ とりあえず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の中に、処理を書くだけです。

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));
    }
  });

};


たぶんこんな感じ。

これで、もっかい

command-line
$ sls deploy -v

deployしますと
べろべろーっつって
スクリーンショット 2017-01-26 0.18.20.png

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

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

dynamoDB
スクリーンショット 2017-01-26 0.22.28.png

API Gateway
スクリーンショット 2017-01-26 0.22.41.png

Lambda
スクリーンショット 2017-01-26 0.22.51.png

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

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

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というデータをつくってくれるはずです。

スクリーンショット 2017-01-26 0.34.09.png

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

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

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

とすると

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は

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

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

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

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

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

Cocos2d-xでの通信処理

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);


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