Lambda ↔ DynamoDB


はじめに

Lambda から Rubyaws-sdk つかって DynamoDB な話です。何番煎じやねんと思いつつ、本日付のものは一つしかないだろうということで書きます。

ゴール的なもの

  • LambdaつかってサーバレスでクラウドネイティブなWebのバックエンドをブラウザ開発してみる
  • 今回はとりあえず、Lambda から Rubyaws-sdk つかって DynamoDB へというとこまでやる
  • 最終的には、AWSのあれやこれやのサービスをつなぎ合わせてアプリ作る

開発の流れ

  1. DynamoDBにテーブルつくる
  2. IAMでDynamoにアクセスできるようなロール作る
  3. ロールをLambdaに適用する
  4. Cloud9上で、aws-sdkつかってDynamoDBを叩くコード書く
  5. サーバへのデプロイとかテストとかもWebUIで簡単にできる(ショートカット覚えると快適)

以下、もうちょっと具体的にかいてみます1

DynamoDB

  1. コンソールから DynamoDB > テーブル > テーブルの作成 でテーブル名とプライマリーキーを決めてテーブル作る
  2. DynamoDB > テーブル > 項目 でWebUI上でレコード足せる(今回は、project-id name is-default というカラムを作成)

考えるところはあんまりない

IAM

  1. AmazonDynamoDBFullAccessAWSLambdaDynamoDBExecutionRole の二つのポリシーをロール( ここでは LambdaDynamoDBという名前にした )にアタッチする

Lambda

  1. Lambda > 関数 > 関数の作成 > 一から作成ランタイムRuby2.7にして関数を作る( ここでは project という名前にした )
  2. Lambda > 関数 > project > アクセス権限 で先に作っておいたロール LambdaDynamoDB を付与する
  3. よし、関数にコード書き始めるよ

Webバックエンドになるようなコードだと、以下の lambda_handler にロジックを書くことになる。

lambda_handler
def lambda_handler(event:, context:)
  # Your cool code is here
  { statusCode: 200, body: event }
end

ちなみに、event はクライアントから送ってくるデータが Hash で入っていて、context は関数のコンテキストが入っている。なにそれ?なときは puts context.inspect とかやって中身を見ましょう。初めのうちは気にしなくていいと思います。

context
#<LambdaContext:0x00000000017d3830 @clock_diff=1607145893186, @deadline_ms=1607146470672, @aws_request_id="ea9e9b24-aec1-49ab-8d91-3d8eacc8c3e7", @invoked_function_arn="arn:aws:lambda:us-east-2:578170637269:function:qiita", @log_group_name="/aws/lambda/qiita", @log_stream_name="2020/12/05/[$LATEST]04dae62a2b914b20a6097187ac047ec6", @function_name="qiita", @memory_limit_in_mb="128", @function_version="$LATEST">

適当にコード書いたら Save > Deploy > Test > Edit data な 開発サイクルに入ります。

Command Shortcut
Save + s
Deploy + shift + u
Test + i
Edit data + j

コード

とりあえず、DynamoDB に対して CRUD するものを書いてみます

{
  "project-id": 2,
  "name": "Running",
  "is-default": false
}

みたいなデータが送られてきた時に以下のように CRUD します

CRUD
require 'json'
require 'aws-sdk-dynamodb'


def add_project(table, event)
  table.put_item({ item: event })  
end

def delete_project(table, event)
  params = { table_name: table, key: { 'project-id': event['project-id'] } }
  begin
    table.delete_item(params)
  rescue Aws::DynamoDB::Errors::ServiceError => error
    puts error.message
  end
end

def update_project(table, event)
  params = {
    table_name: table,
    key: { 'project-id': event['project-id'] },
    attribute_updates: {
      name: {
        value: body['name'],
        action: "PUT"
      }
    }
  }
  table.update_item(params)  
end

def list_project(table)
  scan_output = table.scan({ limit: 50, select: "ALL_ATTRIBUTES" })

  scan_output.items.each do |item|
    keys = item.keys

    keys.each do |k|
      puts "#{k}: #{item[k]}"
    end
  end
end

def lambda_handler(event:, context:)
  http_method = 'POST'

  dynamodb = Aws::DynamoDB::Resource.new(region: 'us-east-2')
  table = dynamodb.table('project')

  case http_method
    when 'GET'    then list_project(table)
    when 'PUT'    then update_project(table, event)
    when 'POST'   then add_project(table, event)
    when 'DELETE' then delete_project(table, event)
    else 0
  end

  { statusCode: 200, body: list_project(table) }
end
  • aws-sdk で簡単に書くことができる
  • Aws::DynamoDB::Resource vs. Aws::DynamoDB::Client についてはもう少し調べてみたい
  • HTTPメソッドごとに処理を切り替える部分は(今後、接続するであろう)API Gateway から Lamda に context.http_method という形で HTTPメソッド(GET/PUT/POST/DELETE)が送られてくることを想定(ベストプラクティスはなんだろう? CRUD 個々に関数ファイル分けるのが一般的?それだとAPI Gatewayの設定が増えてちょっとめんどくさそう)
  • attribute_updates より UpdateExpression を使いなさいという記載も見かけるので、後で変更も考えたい(個人的には attribute_updates の方が読みやすく感じるが)

STDOUT が見たい

コード書いていると、pputs した STDOUT をみたくなる。手っ取り早い方法は、AWS CLI を使うことだと思う。Lambda のログは、デフォルトで Cloud Watch に飛ばされているようで、以前はみるのがややめんどくさかった2 が、今は以下で簡単に最新のログをみることができる

aws logs tail /aws/lambda/project

やってみた感じ

  • アカウントつくってからコンソールログインして、Lambda で Hello World まで 5 分くらい
  • Lambda は最初のリリースから 10 年くらいたってるみたいなんですが、かなりこなれてきている印象
  • WebUI での開発もいい感じ。Cloud9 の買収・サーバレス開発環境への統合がワークしていると思う。エディタ部分をフルスクリーンにして、vim キーバインド、ショートカット覚えると集中できる
  • aws-sdk つかって Dynamo へ簡単な CRUD 書くまでで1, 2時間って感じ。生産性はめっちゃいい

参考にしたサイト

Cheers,

シリーズ


  1. ここのページもステップ・バイ・ステップで手順がまとまっている 

  2. aws logs get-log-events --log-group-name /aws/lambda/project --log-stream-name '2020/12/01/[$LATEST]3cc66941ad844ba1afb5659ae677410b' --limit 100 みたいに ログ・グループとログ・ストリームの指定が必要だった。で、ログ・ストリームの値は関数実行ごとに毎回変わるのでめんどくさかった