AWS Lambda+API Gateway+DynamoDBでCRUD APIを作るのをGolangでやってみた

# この記事について
Developers.IO 2020のサーバーレスセッションに触発されました。
[[動画公開] 初めてのサーバーレスアプリケーション開発 #devio2020](https://dev.classmethod.jp/articles/devio2020-first-serverless-application/)

というわけで、Golangを用いてAWSで基本的なサーバーレスをやってみたその手順をまとめました。
具体的には以下の手順を紹介します。

1. GolangでLambdaを動かしAPI Gatewayと連携させる
2. LambdaとDynamoDBと連携してAPIを作る

## 使用する環境・バージョン
- OS : macOS Mojave 10.14.5
- Golang :  version go1.14 darwin/amd64

## 読者に求める前提知識
Golangの基本的な文法がわかること。

# Lambda関数の作成
## コンソールで関数を作成
AWS Lambdaのコンソールを開くと、以下のような画面になります。
<img width="1440" alt="スクリーンショット 2020-07-28 19.21.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/04362ecd-a2e6-aeee-0559-ae42b111135c.png">
右上にある「関数の作成」ボタンをクリックします。
すると、以下のような関数作成画面に遷移します。

<img width="1440" alt="スクリーンショット 2020-07-28 19.23.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/e7c349df-b304-9c2b-87ba-f6bbe30e9bbb.png">
「一から作成」を選択し、関数名・ランタイムを記入します。今回は以下のように設定しました。

- 関数名: 好きな名前を入力(今回はmyTestFunction)
- ランタイム: Go1.x

次にLambda関数のアクセス権限の設定をします。
「実行ロールの選択または作成」のプルダウンを開くと、以下のようなフォームが表れます。
<img width="1363" alt="スクリーンショット 2020-07-28 19.24.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/591a8f14-4494-dc06-c79e-d661142ccabc.png">
今回Lambdaを動かすのは初めてなので、「基本的なLambdaアクセス権限で新しいロールを作成」を選択します。
これで、Lambda関数作成時に、関数のログをCloudWatchに出力するためのロールが作られます。

ここまでの入力が終わったら、関数を作成します。
正常に作成されたら、以下のような画面になります。
<img width="1440" alt="スクリーンショット 2020-07-28 19.25.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/47089d1a-71ab-0d69-5536-5be5ccb9b2da.png">
補足: このとき関数と同時に作られたロールとCloudWatchのロググループは、このLambda関数を削除しても消されず残ったままになります。つまり、ロール・ロググループの削除は、Lambda関数の削除とは別に手動で行う必要があるということです。

## 関数のコードを作成
作ったばかりの関数の中身は「hello,world」を返すだけのデフォルト状態なので、これからLambda上で動かしたいプログラムを別に書いてやる必要があります。

今は手始めに「httpリクエストを受けたら、httpメソッド・リクエストボディ・パスパラメータ・クエリパラメータをjsonにして返す」という関数を作成してみます。

まずは、ローカルに必要なライブラリをインストールします。

```bash
go get -u github.com/aws/aws-lambda-go/lambda
go get -u github.com/aws/aws-lambda-go/events
```
インストールしたら、コードを書いていきます。

```hello.go
package main

import (
	"encoding/json"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

type Response struct {
	RequestMethod  string `json:"RequestMethod"`
	RequestBody    string `json:"RequestBody"`
	PathParameter  string `json:"PathParameter"`
	QueryParameter string `json:"QueryParameter"`
}

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    // httpリクエストの情報を取得
	method := request.HTTPMethod
	body := request.Body
	pathParam := request.PathParameters["pathparam"]
	queryParam := request.QueryStringParameters["queryparam"]

    // レスポンスとして返すjson文字列を作る
	res := Response{
		RequestMethod:  method,
		RequestBody:    body,
		PathParameter:  pathParam,
		QueryParameter: queryParam,
	}
	jsonBytes, _ := json.Marshal(res)

    // 返り値としてレスポンスを返す
	return events.APIGatewayProxyResponse{
		Body:       string(jsonBytes),
		StatusCode: 200,
	}, nil
}

func main() {
	lambda.Start(handler)
}
```
参考:[AWS公式ドキュメント Go の AWS Lambda 関数ハンドラー](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-handler.html)
参考:[Go+Lambdaで最速サーバレスチュートリアル](https://qiita.com/shunp/items/52ee20278e02e29ef3e8)

ここで、コードについていくつか解説します。
### main関数について
Lambdaで実行されるのはmain関数です。そのため、Lambdaにアップロードするコードには必ずmain関数を用意してやる必要があります。
今回は、`handler`というAPIハンドラ(関数)を起動する操作をmain関数に書きました。

### API Gatewayからhttpリクエストの情報を取得する方法
ハンドラは`events.APIGatewayProxyRequest`型の変数`request`を引数にとっています。この変数`request`の中に、どのようなhttpリクエストを受け取ったかの情報が格納されています。
例えば、今回の場合は以下のように情報を取得しています。

- リクエストメソッド: `request.HTTPMethod`で取得
- リクエストボディ: `request.Body`で取得
- パスパラメータ(`/{pathparam}`とAPI Gatewayで設定した部分): `equest.PathParameters["pathparam"]`で取得
- クエリパラメータ(`/?queryparam=`の部分): `request.QueryStringParameters["queryparam"]`で取得

`events.APIGatewayProxyRequest`型の定義をGo Docで確認すると、他にもどのようなフィールドがあるのかがわかります。やりたい処理に合わせて活用すればよいでしょう。

```go
type APIGatewayProxyRequest struct {
    Resource                        string                        `json:"resource"` // The resource path defined in API Gateway
    Path                            string                        `json:"path"`     // The url path for the caller
    HTTPMethod                      string                        `json:"httpMethod"`
    Headers                         map[string]string             `json:"headers"`
    MultiValueHeaders               map[string][]string           `json:"multiValueHeaders"`
    QueryStringParameters           map[string]string             `json:"queryStringParameters"`
    MultiValueQueryStringParameters map[string][]string           `json:"multiValueQueryStringParameters"`
    PathParameters                  map[string]string             `json:"pathParameters"`
    StageVariables                  map[string]string             `json:"stageVariables"`
    RequestContext                  APIGatewayProxyRequestContext `json:"requestContext"`
    Body                            string                        `json:"body"`
    IsBase64Encoded                 bool                          `json:"isBase64Encoded,omitempty"`
}
```
参考:[GoDoc package aws/aws-lambda-go/events](https://godoc.org/github.com/aws/aws-lambda-go/events)

### API Gatewayにレスポンスを返す方法
ハンドラは返り値に`events.APIGatewayProxyResponse`型をとります。なので、所望のレスポンス内容に沿ったこの型の変数を作成するのが、ハンドラ内で行う処理内容です。
`events.APIGatewayProxyResponse`型の定義は以下のようになっています。

```go
type APIGatewayProxyResponse struct {
    StatusCode        int                 `json:"statusCode"`
    Headers           map[string]string   `json:"headers"`
    MultiValueHeaders map[string][]string `json:"multiValueHeaders"`
    Body              string              `json:"body"`
    IsBase64Encoded   bool                `json:"isBase64Encoded,omitempty"`
}
```
参考:[GoDoc package aws/aws-lambda-go/events](https://godoc.org/github.com/aws/aws-lambda-go/events)

今回の場合は、以下のようにレスポンスを作っています。

- StatusCode: httpレスポンスコード200を指定
- Body: 自作の構造体(`Response`)から`json.Marshal`→`string`と変換

## コードをLambdaにアップロード
Lambdaにアップロードするのはコンパイル済みの実行ファイルである必要があるので、上で書いた`hello.go`をビルドしてバイナリファイル`hello`を作ります。
また、アップロードの形式がzipファイルなので、ビルド後に`hello`をzip圧縮します。

```bash
$ GOOS=linux GOARCH=amd64 go build -o hello hello.go
$ zip hello.zip hello
```
参考:[AWS Lambda× Goを試す](https://qiita.com/kurkuru/items/29a8f6de6890bbc887a4)

先ほど作った`myTestFunction`関数をLambdaコンソールで開き、以下の設定画面から`hello.zip`をアップロードします。
<img width="1328" alt="スクリーンショット 2020-07-28 21.35.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/cd2bb7c5-5a49-9b49-991f-38b93db71411.png">

### 注意:アップロードする実行ファイルの名前
「一から作成」のオプションから作成したLambda関数に渡す実行ファイルの名前は、必ず`hello`である必要があります。
これは一から作成のLambda関数がデフォルトで「`hello`という名前のバイナリファイルを実行する」という設定になっているため、他の名前だと以下のようなPathErrorが起きます。

```
{
   “errorMessage”: “fork/exec /var/task/binaryname: permission denied”, 
   “errorType”: “PathError”
}
```

## Lambdaのテスト
Lambda関数は、Webコンソール上でテストを実行することができます。
<img width="1305" alt="スクリーンショット 2020-08-01 14.32.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/992f0ce0-5e88-0b67-b854-2b1e4ef5023b.png">
コンソール上で、右上の「テスト」のボタンをクリックします。
すると、テストリクエストを編集する画面が表れます。
<img width="816" alt="スクリーンショット 2020-07-29 14.28.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/8620fb20-ffdf-8576-5f47-e6e1b222313d.png">
デフォルトだとこのような状態です。このまま名前をつけて保存します。
<img width="1321" alt="スクリーンショット 2020-07-29 15.14.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/8179d5b6-9468-5e43-5d5a-7e3978c9910d.png">
この状態で「テスト」ボタンをクリックすると、テストが実行・結果が表示されます。
<img width="1399" alt="スクリーンショット 2020-08-01 14.33.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/d1119d69-7350-2677-162e-e6f134902eb0.png">
きちんとステータスコード200が返ってくることが確認できました。

補足: このテストはAPI Gateway経由のリクエストを送っているわけではないので、httpメソッドやボディなどのリクエスト情報が空のときの結果が表示されています。

# API Gatewayと連携
## APIの作成
<img width="1440" alt="スクリーンショット 2020-07-28 21.43.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/00482998-e0ee-df20-bbae-59befa97e2e1.png">
開始画面から、「今すぐはじめる」ボタンをクリックします。
すると、以下のようなAPI作成画面になります。
<img width="1440" alt="スクリーンショット 2020-07-28 21.45.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/199981a9-a94d-2773-a6f9-003beeff47bd.png">
以下のような設定を入力して作成します。

- プロトコル: REST
- 新しいAPIの作成: 新しいAPI
- API名: 好きな名前をつける
- 説明: 好きな説明文を書く
- エンドポイントタイプ: リージョン

<img width="1440" alt="スクリーンショット 2020-07-28 21.46.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/b953844e-f3ef-7ad4-cf67-f59dd10f5b39.png">
APIを作成したら、「どのパス・どのメソッドにどの処理を結びつけるか」の設定を行う画面が表示されます。

## URLリソースの作成
まずは、URLリソースの作成を行います。「アクション」→「リソースの作成」を選択します。
<img width="432" alt="スクリーンショット 2020-07-28 21.57.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/ce1c85ea-3b0e-efdb-410e-45442560e3b1.png">
以下のような、パスパラメータの名前等を設定する画面になります。
<img width="1180" alt="スクリーンショット 2020-07-28 22.04.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/867e18d0-2468-fdea-d523-f84bd13b80bb.png">

- プロキシリソース: なし
- リソース名: 好きな名前をつける
- リソースパス: {pathparam}
- API Gateway CORS : なし

以上の設定でリソースを作成します。

## メソッドの作成
次に、httpリクエストメソッドーLambda関数の紐付けを行います。
<img width="319" alt="スクリーンショット 2020-07-28 22.05.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/abb07342-8613-7b25-1737-02b22b5cc081.png">
`/{pathparam}`を選択した状態で、「アクション」→「メソッドの作成」を選択します。
<img width="211" alt="スクリーンショット 2020-08-01 15.21.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/1c47b5ca-7469-be4f-c5c6-ebecd6d05acb.png">
プルダウンから、設定を行いたいリクエストメソッドを選択します。GETやPOSTなどの特定メソッドの選択はもちろん、全てのメソッドに対しての設定を行いたい場合はANYという選択肢もあります。

今回はANYを選択します。
メソッドを選択したら、その選択メソッドにどんな処理(Lambda関数)を紐づけるのかの設定画面が表示されます。
<img width="1184" alt="スクリーンショット 2020-08-01 15.21.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/f401432f-4531-75e6-d741-99a0eeb50f0d.png">

- 統合タイプ: Lambda関数
- Lambdaプロキシ統合の使用: あり
- Lambdaリージョン: ap-northeast-1
- Lambda関数: myTestFunction
- デフォルトタイムアウトの使用: あり

以上の設定で保存をクリックすると、以下のような確認画面が出ます。
<img width="904" alt="スクリーンショット 2020-07-28 21.58.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/0186a18c-5b49-de47-2d17-28d86a77cf14.png">
問題ないので、OKを選択します。結果は以下の通り。
<img width="1187" alt="スクリーンショット 2020-08-01 15.22.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/2748b096-b8be-efcb-8df7-394db8f11857.png">

## テストの実行
上の画面で「テスト⚡️」をクリックすることで、API Gatewayからリクエストを送ったときにきちんと動くかどうかのテストを実行することができます。

<img width="1187" alt="スクリーンショット 2020-07-31 14.54.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/603946f3-4306-e50a-9258-0b0d9a877c77.png">
このように、パスパラメータ・クエリパラメータ・リクエストメソッド・ボディなどを自由にコンソール上で設定して、それに対してどのような応答が返ってくるのかを確認することができます。

## APIのデプロイ
実際にAPIをデプロイするには、「アクション」→「APIのデプロイ」を選択します。
<img width="317" alt="スクリーンショット 2020-07-28 22.22.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/2f09fcc6-3113-63bf-352e-33dbc7cedd8a.png">
すると、デプロイステージを指定する画面になります。
<img width="602" alt="スクリーンショット 2020-07-28 22.24.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/3b7f9814-bab2-9822-379e-4f71ee5a866e.png">
ステージを指定して「デプロイ」ボタンを押すと、以下のような画面に遷移します。
ここで、APIが公開されているURLを確認することができます。
<img width="1440" alt="スクリーンショット 2020-07-28 22.25.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/f347f1bf-127b-db69-96e4-573c3f9b4594.png">

参考:[【AWS】API Gateway + LambdaでAPIをつくる](https://qiita.com/flySheep/items/34b1df75d06eb6e6b288)

# DynamoDBと連携
ここからは、[[動画公開] 初めてのサーバーレスアプリケーション開発 #devio2020](https://dev.classmethod.jp/articles/devio2020-first-serverless-application/)で紹介された、以下のようなCRUDを行うDB連携APIを新しく作っていきます。
<img width="968" alt="スクリーンショット 2020-07-31 15.13.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/065a2cbd-b69b-1205-e9ff-b8d6507e5fa8.png">

## テーブル作成
<img width="931" alt="スクリーンショット 2020-07-31 15.42.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/74a9373a-240e-687a-b5ee-3b0c5d783032.png">
テーブル作成画面で、以下のような設定を入力して作成します。

- テーブル名: 好きな名前(ここではuser)
- プライマリーキー: userid(数値)
- ソートキー: 追加しない
- テーブル設定: デフォルト設定の使用

補足: プライマリーキーに設定できるデータ型は文字列orバイナリor数値です。

<img width="1427" alt="スクリーンショット 2020-07-31 15.58.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/c0475e8a-9790-350e-2c40-87e0b1e1d3fb.png">
作成に成功すると、以下のような画面に遷移します。

## データ項目の追加
作成直後は、プライマリーキー以外の属性が存在しないので、他のキーが欲しいのならば手動で追加する必要があります。
テーブルの項目タブを開きます。
<img width="1069" alt="スクリーンショット 2020-07-31 16.04.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/0c92fc96-f123-3b61-78bd-e1fad796ed05.png">
「項目の作成」ボタンをクリックすると、以下のような項目編集画面が表示されます。
<img width="404" alt="スクリーンショット 2020-07-31 16.05.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/7a856825-8195-c5fc-671e-c8a4d50a48ca.png">
+をクリックします。
<img width="320" alt="スクリーンショット 2020-07-31 16.05.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/ae371cd1-6838-6417-dda8-fe4c2cb38d1f.png">
Appendを選択します。
<img width="308" alt="スクリーンショット 2020-07-31 16.05.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/c1c70a8a-8a33-cf31-cb2a-f1cd3d52ca9a.png">
すると、どういう型のフィールドを追加するかを選択できます。
今回はStringを選択しました。
<img width="334" alt="スクリーンショット 2020-07-31 16.06.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/9ed31c2c-f8ae-7590-3707-80ccab17131a.png">
すると、String型のフィールドが追加されました。好きにフィールド名をつけたり、値も設定を行ったりします。
<img width="1183" alt="スクリーンショット 2020-07-31 16.08.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/c82757c0-33a6-14fd-e427-34d6459383fd.png">
項目の追加を複数回行い、最終的にこうなりました。これで保存します。
<img width="1084" alt="スクリーンショット 2020-07-31 16.08.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/98a54a5a-587d-0d08-2411-b7c1c400d3b1.png">
無事にプライマリーキー以外の項目が作成されたことが確認できます。

参考:[初めてのサーバーレスアプリケーション開発 ～DynamoDBにテーブルを作成する～](https://dev.classmethod.jp/articles/serverless-first-dynamodb/)

## Lambda用のロールを作成
Lambda関数がDynamoDBにアクセスできるように、Lambda関数に付与するロールを作成します。

IAMコンソール→ロールから作成画面を開きます。
<img width="981" alt="スクリーンショット 2020-07-31 16.26.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/779f986f-13fd-80d3-81bd-2e752b317d71.png">
AWSサービス、Lambdaを選択します。
そして、以下のアクセス権限を追加して、ロールを作成します。

- AmazonDynamoDBFullAccess
- AWSLambdaDynamoDBExecutionRole

DynamoDBとの接続を必要とするLambda関数には、今作ったロールを付加します。

参考:[初めての、LambdaとDynamoDBを使ったAPI開発](https://qiita.com/hellscare/items/d80c9ff0290966eb0cf8)

## Lambda関数のコードを作成
いよいよDBにアクセスしてCRUD操作を行う関数コードを書いていきます。

まず、必要なパッケージをダウンロードします。

```bash
$ go get -u github.com/aws/aws-sdk-go/aws
$ go get -u github.com/aws/aws-sdk-go/aws/session
$ go get -u github.com/aws/aws-sdk-go/service/dynamodb
```

### Create(POST)の作成
```go
package main

import (
	"encoding/json"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

// Item DBに入れるデータ
type Item struct {
	UserID  int    `dynamodbav:"userid" json:userid`
	Address string `dynamodbav:"address" json:address`
	Email   string `dynamodbav:"email" json:email`
	Gender  string `dynamodbav:"gender" json:gender`
	Name    string `dynamodbav:"name" json:name`
}

// Response Lambdaが返答するデータ
type Response struct {
	RequestMethod string `json:RequestMethod`
	Result        Item   `json:Result`
}

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	method := request.HTTPMethod

    // DBと接続するセッションを作る→DB接続
	sess, err := session.NewSession()
	if err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, err
	}

	db := dynamodb.New(sess)

	// リクエストボディのjsonから、Item構造体(DB用データの構造体)を作成
	reqBody := request.Body
	resBodyJSONBytes := ([]byte)(reqBody)
	item := Item{}
	if err := json.Unmarshal(resBodyJSONBytes, &item); err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, err
	}

	// Item構造体から、inputするデータを用意
	inputAV, err := dynamodbattribute.MarshalMap(item)
	if err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, err
	}
	input := &dynamodb.PutItemInput{
		TableName: aws.String("user"),
		Item:      inputAV,
	}

	// insert実行
	_, err = db.PutItem(input)
	if err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, err
	}

    // httpレスポンス作成
	res := Response{
		RequestMethod: method,
	}
	jsonBytes, _ := json.Marshal(res)

	return events.APIGatewayProxyResponse{
		Body:       string(jsonBytes),
		StatusCode: 200,
	}, nil
}

func main() {
	lambda.Start(handler)
}
```
ここでやっていることは以下の操作です。

1. DBに接続
2. リクエストボディからItem構造体を作る
3. Item構造体から、DBにinsertするためのデータを作る
4. insertを実行する
5. レスポンスを作成

#### 1. DBに接続
以下のコードが該当します。

```go
sess, err := session.NewSession()
if err != nil {
	return events.APIGatewayProxyResponse{
		Body:       err.Error(),
		StatusCode: 500,
	}, err
}

db := dynamodb.New(sess)
```
これは、DynamoDBに接続する処理を行うときには必ず必要な定型句といってもいいでしょう。

#### 2. リクエストボディからItem構造体を作る
httpリクエストボディに、DBに挿入したいデータがjson形式で格納されています。

```example-requestbody.json
{
    "userid": 2,
    "address": "Osaka",
    "email": "bbb.jp",
    "gender": "F",
    "name": "Nancy"
}
```
`request.Body`で得られるリクエストボディはstring型なので、これを`json.Unmarshal`でパースして構造体形式(上だとItem構造体)に変換することで、ボディに格納されているデータを扱えるようにします。

#### 3. Item構造体から、DBにinsertするためのデータを作る
DynamoDBにデータを挿入する関数は、`db.PutItem()`です。

```go
func (c *DynamoDB) PutItem(input *PutItemInput) (*PutItemOutput, error)
```
参考:[GoDoc github.com/aws/aws-sdk-go/service/dynamodb#DynamoDB.PutItem](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb#DynamoDB.PutItem)
しかし、見ての通りこの関数の引数は`dynamodb.PutItemInput`型なので、Item型をそのままDBに渡すことはできません。そのため、Item型を`dynamodb.PutItemInput`型に変換する必要があります。

`dynamodb.PutItemInput`型の定義を見てみましょう。

```go
type PutItemInput struct {
    // 今回関係ないフィールドを省略
    Item map[string]*AttributeValue `type:"map" required:"true"`
    TableName *string `min:"3" type:"string" required:"true"`
}
```
参考:[GoDoc github.com/aws/aws-sdk-go/service/dynamodb#PutItemInput](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb#PutItemInput)

`TableName`は、データを追加したDynamoDBのテーブル名を指定するフィールドです。
追加するデータの内容を入れるフィールドは、`map[string]*AttributeValue`型の`Item`です。つまり、2でリクエストボディから作った`Item`型構造体を、この`map[string]*AttributeValue`型に変換してやる必要があるわけです。

まさに、この変換を行う関数が公式から提供されています。`dynamodbattribute.MarshalMap`という関数です。

```go
// Item型のitem変数を、map[string]*AttributeValue型のimputAVに変換
inputAV, err := dynamodbattribute.MarshalMap(item)
```

この変換を正しく行うためには、Item型構造体に指定のメタタグをつける必要があります。
(`json.Unmarshal`でjsonを構造体にパースするために、構造体にjsonタグをつけたのと同じ論理です)
ここで、Item型を以下のように定義していました。

```go
type Item struct {
	UserID  int    `dynamodbav:"userid" json:userid`
	Address string `dynamodbav:"address" json:address`
	Email   string `dynamodbav:"email" json:email`
	Gender  string `dynamodbav:"gender" json:gender`
	Name    string `dynamodbav:"name" json:name`
}
```
この構造体の各フィールドにつけている`dynamodbav`タグは、「各フィールドがDynamoDBのどのキーに対応しているか」ということを示しています。例えば、UserIDフィールドは、DynamoDBではuseridキーに紐づいています。

`dynamodbattribute.MarshalMap`を実行するためには、各フィールドにこの`dynamodbav`タグを確実につけましょう。

#### 4. insertを実行する
`db.PutItem()`を実行します。

#### 5. レスポンスを作成
APIを作成したときと要領は同じなので割愛します。

### Read(GET)の作成
```go
package main

import (
	"encoding/json"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

// Item構造体とResponse構造体は、Createのときと同じなので割愛

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	method := request.HTTPMethod
	pathparam := request.PathParameters["userid"]

    // DB接続
	sess, err := session.NewSession()
	if err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, err
	}

	db := dynamodb.New(sess)

	// 検索条件を用意
	getParam := &dynamodb.GetItemInput{
		TableName: aws.String("user"),
		Key: map[string]*dynamodb.AttributeValue{
			"userid": {
				N: aws.String(pathparam),
			},
		},
	}

	// 検索
	result, err := db.GetItem(getParam)
	if err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 404,
		}, err
	}

	// 結果を構造体にパース
	item := Item{}
	err = dynamodbattribute.UnmarshalMap(result.Item, &item)
	if err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, err
	}

    // httpレスポンス作成
	res := Response{
		RequestMethod: method,
		Result:        item,
	}
	jsonBytes, _ := json.Marshal(res)

	return events.APIGatewayProxyResponse{
		Body:       string(jsonBytes),
		StatusCode: 200,
	}, nil
}

// main関数はCreateのときと同じなので割愛
```
ここでやっていることは以下の操作です。

1. DBに接続(説明割愛)
2. DBに問い合わせる検索条件を作る
3. DBに問い合わせてデータを取得する
4. レスポンスを作成(説明割愛)

#### 2. DBに問い合わせる検索条件を作る
DBに問い合わせてデータを取得する関数は`db.GetItem()`です。

```go
func (c *DynamoDB) GetItem(input *GetItemInput) (*GetItemOutput, error)
```
参考:[GoDoc github.com/aws/aws-sdk-go/service/dynamodb#DynamoDB.GetItem](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb#DynamoDB.GetItem)
この引数としてとるのは`dynamodb.GetItemInput`型なので、この型の変数を作成します。

`dynamodb.GetItemInput`型の定義を確認します。

```go
type GetItemInput struct {
    // 今回関係ないフィールドを省略
    Key map[string]*AttributeValue `type:"map" required:"true"`
    TableName *string `min:"3" type:"string" required:"true"`
}
```
`TableName`はCreateのときと同様に、対象テーブルの名前を指定するフィールド、`Key`は、取得したいレコードのプライマリーキーを指定するフィールドです。
そのため、`db.GetItem()`に渡す`dynamodb.GetItemInput`型引数を以下のように作成します。

```go
getParam := &dynamodb.GetItemInput{
	TableName: aws.String("user"),
	Key: map[string]*dynamodb.AttributeValue{
		"userid": {
			N: aws.String(pathparam),
		},
	},
}
```
このコードの意味は以下の通りです。

- TableName: userテーブルを検索
- Key: ここでは、Number型(N)のキーであるuseridの値が、`aws.String(pathparam)`であるデータを検索するという意味

`Key`フィールドで、useridキーが数値型であることを、`N`という風に指定しています。各データ型がどの表現に対応するのかは以下の表をご覧ください。

|データ型|アルファベット|
|:--|:--|
|バイナリ|B|
|ブール型|BOOL|
|バイナリセット|BS|
|リスト|L|
|マップ|M|
|数値|N|
|数値セット|NS|
|null|NULL|
|文字列|S|
|文字列セット|SS|

参考:[AWS公式ドキュメント AttributeValue](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html)


また、`Key`フィールドは、`dynamodb.PutItemInput`型の`Item`フィールドと同じく`map[string]*AttributeValue`型なのです。しかし、Createのときと同様に`Key`フィールドを`dynamodbattribute.MarshalMap`関数から作ろうとしてもうまくいきません。

```go
// ダメな例
searchItem := Item{UserID: userid}
searchAV, _ := dynamodbattribute.MarshalMap(searchItem)

getParam := &dynamodb.GetItemInput{
	TableName: aws.String("user"),
	Key: searchAV,
}
```
これはおそらく`dynamodb.PutItemInput.Item`フィールドとは異なり、`Key`フィールドには「プライマリーキーの情報だけを含めなければいけない」という仕様が関係していると推測されます。
`dynamodbattribute.MarshalMap(Item{UserID: userid})`には、主キーであるUserID以外にも、ゼロ値に設定された他フィールドが含まれてしまっているのでうまくいかないんだと思います。

#### 3. DBに問い合わせてデータを取得する
`db.GetItem()`を実行すればOKです。

### Update(PUT)の作成
```go
package main

import (
	"encoding/json"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/expression"
)

// Item構造体とResponse構造体は、Createのときと同じなので割愛

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	method := request.HTTPMethod
	pathparam := request.PathParameters["userid"]

	// まずはDBと接続するセッションを作る
	sess, err := session.NewSession()
	if err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, err
	}

	db := dynamodb.New(sess)

    // リクエストボディのjsonから、Item構造体を作成
	reqBody := request.Body
	resBodyJSONBytes := ([]byte)(reqBody)
	item := Item{}
	if err := json.Unmarshal(resBodyJSONBytes, &item); err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, err
	}

	// updateするデータを作る
	update := expression.UpdateBuilder{}
	if address := item.Address; address != "" {
		update = update.Set(expression.Name("address"), expression.Value(address))
	}
	if email := item.Email; email != "" {
		update = update.Set(expression.Name("email"), expression.Value(email))
	}
	if gender := item.Gender; gender != "" {
		update = update.Set(expression.Name("gender"), expression.Value(gender))
	}
	if name := item.Name; name != "" {
		update = update.Set(expression.Name("name"), expression.Value(name))
	}
	expr, err := expression.NewBuilder().WithUpdate(update).Build()
	if err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, err
	}

	input := &dynamodb.UpdateItemInput{
		TableName: aws.String("user"),
		Key: map[string]*dynamodb.AttributeValue{
			"userid": {
				N: aws.String(pathparam),
			},
		},
		ExpressionAttributeNames: expr.Names(),
		ExpressionAttributeValues: expr.Values(),
		UpdateExpression: expr.Update(),
	}

	// update実行
	_, err = db.UpdateItem(input)
	if err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, err
	}

    // httpレスポンス作成
	res := Response{
		RequestMethod: method,
	}
	jsonBytes, _ := json.Marshal(res)

	return events.APIGatewayProxyResponse{
		Body:       string(jsonBytes),
		StatusCode: 200,
	}, nil
}

// main関数はCreateのときと同じなので割愛
```
ここでやっていることは以下の操作です。

1. DBに接続(説明割愛)
2. リクエストボディからItem構造体を作る
3. DBのデータをどう更新するかを指定する
4. update実行
5. レスポンスを作成(説明割愛)

#### 2. リクエストボディからItem構造体を作る
Createのときとやり方は全く同じです。

今回は、パスパラメータで指定されたuseridレコードの、emailとnameを更新したくて以下のようなリクエストボディがきたと仮定します。

```example-requestbody.json
{
    "email": "ccc.com",
    "name": "Emily"
}
```

#### 3. DBのデータをどう更新するかを指定する
更新を行う`db.UpdateItem`の引数となる`dynamodb.UpdateItemInput`型の変数を作成します。
`dynamodb.UpdateItemInput`型の定義は以下の通りです。

```go
type UpdateItemInput struct {
    // 今回関係ないフィールドを省略
    ExpressionAttributeNames map[string]*string `type:"map"`
    ExpressionAttributeValues map[string]*AttributeValue `type:"map"`
    Key map[string]*AttributeValue `type:"map" required:"true"`
    TableName *string `min:"3" type:"string" required:"true"`
    UpdateExpression *string `type:"string"`
}
```
参考:[GoDoc github.com/aws/aws-sdk-go/service/dynamodb#UpdateItemInput](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb#UpdateItemInput)

`Key`と`TableName`についてはReadのときと意味は同様です。

残り3つのフィールドについては、データの更新の種類・やり方について記述する場所です。「データの更新」といっても、ただ今ある値を捨てて新しい値に書き換えるだけではなく、データ型によって様々な操作が考えられます。主たる例を以下に挙げます。

- 数値型を収める属性Aを、Bという値に上書き保存したい
- 属性Aが保持しているリストに、Bという値を追加したい
- 指定したレコードから属性Aを消したい
- 属性Aが保持しているセット型から、Bというセットを消したい

参考:[DynamoDBでデータを更新する際に使うUpdateExpressionについて一通りまとめてみた](https://dev.classmethod.jp/articles/dynamodb-update-expression-actions/)

そのため、「その属性を操作したいか」を`ExpressionAttributeNames`に、「上書きしたり追加したりしたい値」を`ExpressionAttributeValues`に、「上書きなのか追加なのかという更新の種類」を`UpdateExpression`に記述するのです。

例えば、今回の「"name"という属性を、変数`name`の値に上書きしたい」という操作を**ドキュメントどおり**に記述するのならば以下のようになります。

```go
input := &dynamodb.UpdateItemInput{
	TableName: aws.String("user"),
	Key: map[string]*dynamodb.AttributeValue{
		"userid": {
			N: aws.String(pathparam),
		},
	},
	ExpressionAttributeNames: map[string]*string{
        // "name"という属性名を以下#nameと扱う
		"#name": aws.String("name"),
	},
	ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
        // 上書きしたい値nameを以下:name_valueとして扱う
		":name_value": {
			S: aws.String(name),
		},
	},
    // #name属性を、:name_valueという値に上書き(set)する
	UpdateExpression: aws.String("set #name = :name_value"),
}
```
参考:[あえて aws-sdk-go で dynamoDB を使うときの基本操作](https://qiita.com/ezaki/items/2a9f10c53d958070ca95)

しかし、属性名を`#`で指定したり、更新したい値を`:`で指定したりするドキュメントどおりの書き方は少々面倒です。
そのため、これらの構造表現をコードベースで生成するパッケージが公式から提供されています([github.com/aws/aws-sdk-go/service/dynamodb/expression](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb/expression))。せっかくなのでその方法に書き換えていきましょう。

まずは、`expression.UpdateBuilder{}`という型の構造体を用意して、その型のメソッドを用いて「どう更新したいのか」を記述します。

```go
update := expression.UpdateBuilder{}
if name := item.Name; name != "" {
	update = update.Set(expression.Name("name"), expression.Value(name))
}
```
上の部分は、「DBの"name"という属性を、`name`という変数の中身に上書きする」という操作を、`expression.UpdateBuilder{}`型の`update`に記録しています。

このupdateの内容を指定し終わったら、updateの内容を`ExpressionAttributeNames`等のフィールドに入れられる形に変換します。

```go
expr, err := expression.NewBuilder().WithUpdate(update).Build()
```
このexprを使って、`db.UpdateItem`を作ると以下のようになります。

```go
input := &dynamodb.UpdateItemInput{
	TableName: aws.String("user"),
	Key: map[string]*dynamodb.AttributeValue{
		"userid": {
			// Nはnumber型の意味
			N: aws.String(pathparam),
		},
	},
	ExpressionAttributeNames: expr.Names(),
	ExpressionAttributeValues: expr.Values(),
	UpdateExpression: expr.Update(),
}
```

#### 4. update実行
`db.UpdateItem`を実行すればOKです。

### Delete(DELETE)の作成
```go
package main

import (
	"encoding/json"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
)

// Item構造体とResponse構造体は、Createのときと同じなので割愛

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	method := request.HTTPMethod
	pathparam := request.PathParameters["userid"]

	// まずはDBと接続するセッションを作る
	sess, err := session.NewSession()
	if err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, err
	}

	db := dynamodb.New(sess)

	// deleteするデータを指定
	deleteParam := &dynamodb.DeleteItemInput{
		TableName: aws.String("user"),
		Key: map[string]*dynamodb.AttributeValue{
			"userid": {
				// Nはnumber型の意味
				N: aws.String(pathparam),
			},
		},
	}

	// delete実行
	_, err = db.DeleteItem(deleteParam)
	if err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, err
	}

    // httpレスポンス作成
	res := Response{
		RequestMethod: method,
	}
	jsonBytes, _ := json.Marshal(res)

	return events.APIGatewayProxyResponse{
		Body:       string(jsonBytes),
		StatusCode: 200,
	}, nil
}

// main関数はCreateのときと同じなので割愛
```

ここでやっていることは以下の操作です。

1. DBに接続(説明割愛)
2. deleteするデータを指定する
3. delete実行
4. レスポンスを作成(説明割愛)

#### 2. deleteするデータを指定する
Read(GET)のときと同じ方法で`dynamodb.DeleteItemInput`型の変数を作り、消去したいデータを指定します。

#### 3. delete実行
`db.DeleteItem()`を実行すればOKです。

## Lambda関数コードの参考文献
参考:[AWS Lambda, API Gateway, DynamoDB, Golang でREST APIを作る](https://qiita.com/funabashi800/items/d878c8d5dc88a1d7fe67)
参考:[DynamoDB×Go連載#2 AWS SDKによるDynamoDBの基本操作](https://future-architect.github.io/articles/20200227/)

## Lambda関数のコードをアップロード→API Gatewayと連携
ここは既にやった手順と同じなので割愛します。
上述した通り、Lambda関数にDynamoDB用のロールを付与するのを忘れないでください。

# まとめ
これでAPI Gateway-Lambda-Dynamo DBの3つを連携させたサーバーレスAPIの構築が完了です。お疲れ様でした。