😕
AWS AmplifyでTODOアプリのバックエンドを作る
はじめに
こんな環境で動くLambdaのRubyコードを書いたという話です。バックエンドサービスの構築自体は前回、Amplifyでざっとつくっていて、LambdaのランタイムをRubyに置き換えています[1]
┌──────────┐ ┌──────────┐ ┌──────────┐
│ │ │ Lambda │ │ │
Client───────► APIGW ├──────► with ├─────► DynamoDB │
│ (REST) │ │ RubySDK │ │ │
└──────────┘ └──────────┘ └──────────┘
API
TODOアプリなので、APIGWのREST APIの定義はこんな感じにしようと思います
GET /tasks List all tasks
GET /task/1 Get a task by id
POST /tasks Create new task
PUT /tasks Update a task
DELETE /tasks/1 Delete a task by id
APIGWの設定自体はAmplifyがやってくれているのですが、コンソールで見るとこんな設定になっています。
/
|_ /todos Main resource. Eg: /todos
ANY Methods: DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT
OPTIONS Allow pre-flight requests in CORS by browser
|_ /{proxy+} Eg: /todos/, /todos/id
ANY Methods: DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT
OPTIONS Allow pre-flight requests in CORS by browser
ANY
で全てのHTTPメソッドをマッチさせて、Lambda proxy integrations
でリクエストをそのままLambdaに流してしまえってやつです。また{proxy+}
をつかって、URLの全てのパスパラメータ(/tasks/100
など)をマッチさせてこれもLambda側に渡します
クライアントが送るデータは、こういう感じのものを想定
payload
{
"task-id": "30",
"is-active": true,
"task-name": "Buy some coffee",
"updated-at": 1616047389,
"created-at": 1616047389,
"user-id": "110"
}
DynamoDB
Amplifyでtask-id
をキーとしてテーブルtodoTable-dev
を作成している[1:1]
Lambda
APIGWの設定を踏まえて、Lambda側のコードはこんな感じ。Rubyは2.7
lambda_handler.rb
require 'json'
require 'json/add/exception' #...❶
require 'aws-sdk-dynamodb'
def add_task(table, body)
begin
table.put_item({ item: body })
list_task(table)
rescue => e
{ statusCode: 500, body: e.to_json } #...❶
end
end
def delete_task(table, task_id)
begin
params = { table_name: table, key: { 'task-id': task_id } }
table.delete_item(params)
list_task(table)
rescue => e
{ statusCode: 500, body: e.to_json }
end
end
def update_task(table, body)
begin
params = {
table_name: table,
key: { 'task-id': body['task-id'] },
attribute_updates: { #...❷
'is-active': { value: body['is-active'], action: "PUT" },
'task-name': { value: body['task-name'], action: "PUT" },
'updated-at': { value: body['updated-at'], action: "PUT" }
}
}
table.update_item(params)
list_task(table)
rescue => e
{ statusCode: 500, body: e.to_json }
end
end
def list_task(table)
begin
scan_output = table.scan({ limit: 50, select: "ALL_ATTRIBUTES" })
{ statusCode: 200, body: JSON.generate(scan_output['items']) }
rescue => e
{ statusCode: 500, body: e.to_json }
end
end
def get_task(table, task_id)
begin
params = { key: { 'task-id': task_id } }
task = table.get_item(params)
{ statusCode: 200, body: JSON.generate(task['item']) }
rescue => e
{ statusCode: 500, body: e.to_json }
end
end
def lambda_handler(event:, context:)
begin
http_method = event['httpMethod']
dynamodb = Aws::DynamoDB::Resource.new(region: 'us-east-2')
table = dynamodb.table('todoTable-dev')
case http_method
when 'GET'
path_param = event.dig('pathParameters', 'proxy') #...❸
if path_param.nil?
list_task(table)
else
get_task(table, path_param)
end
when 'PUT' then update_task(table, JSON.parse(event['body']))
when 'POST' then result = add_task(table, JSON.parse(event['body']))
when 'DELETE' then delete_task(table, event['pathParameters']['proxy']) #...❸
else 0
end
rescue => e
{ statusCode: 500, body: e.to_json }
end
end
- 例外が発生した時に
Internal Server Error
じゃなくて、エラーの中身をException#to_json
としてクライアントに返すようにしている。Exception#to_json
を使うためには、require 'json/add/exception'
が必要。プロダクションではやったらあかんよ - DynamoDBの値更新では
attribute_updates
はすでに非推奨らしい。UpdateExpression
が推奨されているらしいが、こっちのほうが記述がめんどくさいと思う - クライアント→APIGWからくるPath Parameterは
event['pathParameters']['proxy']
に入っている。nil
があり得るので、dig
を使うことで、キーが存在しない場合にエラーとせず、nil
を返すようにしている
こんな感じでとりあえず動きました!Webアプリ作成のハードルもどんどん下がってきてますね
参考にしたサイト
Discussion