Go+API Gateway+Lambda+DynamoDBでサーバレスなAPI

# はじめに

こちらの記事はGoとAPI Gateway + Lambda + DynamoDBでサーバレスなAPIを作る記事となっています。
前回の記事の続きとなっているため、一部手順を省略しています。

https://qiita.com/tkhs1121/items/b1e332acdbc50fad8c45

https://qiita.com/tkhs1121/items/18d8ba86f1afdec13381

# 環境
MacBook Air M1
開発言語 Go

# 手順
## Lambda関数の作成

一から作成のままランタイムを`Go.1.x` アーキテクチャを`x86_64`にして関数の作成をします。
![Lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1391445/e76764b3-3f67-c9c6-092f-efcfdf99e889.png)

### 必要なパッケージのインストール
```
go get -u github.com/aws/aws-sdk-go
```
```
go get -u github.com/aws/aws-lambda-go
```

### ソースコード

https://github.com/tkhs1121/go-serverless-app-3

一部抜粋

```main.go
package main

import (
	"context"

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

func HandleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	req, err := parseRequest(request.Body)
	if err != nil {
		return errorResponse(err, 500)
	}
	if err := checkAmazonURL(req.URL); err != nil {
		return errorResponse(err, 500)
	}

	db, err := newDynamoDB()
	if err != nil {
		return errorResponse(err, 500)
	}
	if err := db.putAmazonURL(req.URL); err != nil {
		return errorResponse(err, 500)
	}

	return events.APIGatewayProxyResponse{
		Body:       "Success",
		StatusCode: 200,
	}, nil
}

func main() {
	lambda.Start(HandleRequest)
}
```

```common.go
package main

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"regexp"
	"strconv"
	"strings"
	"time"

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

type Request struct {
	URL string `json:"url"`
}

func getRandID() string {
	rand.Seed(time.Now().UnixNano())
	return strconv.Itoa(rand.Intn(100))
}

func getEpochTime() string {
	return strconv.FormatInt(time.Now().Unix(), 10)
}

func parseRequest(inputs string) (*Request, error) {
	var req Request
	err := json.NewDecoder(strings.NewReader(inputs)).Decode(&req)
	if err != nil {
		return nil, fmt.Errorf("cannot decode body: %v: %v", err, inputs)
	}
	return &req, nil
}

func checkAmazonURL(url string) error {
	isAmazpnURL, err := regexp.MatchString(`^http(s)?://(www.)?amazon.(co.)?jp[\w!\?/\+\-_~=;\.,\*&@#\$%\(\)'\[\]]*`, url)
	if err != nil {
		return err
	}
	if !isAmazpnURL {
		return fmt.Errorf("this is not amazon url: %v", url)
	}
	return nil
}

func errorResponse(err error, statusCode int) (events.APIGatewayProxyResponse, error) {
	return events.APIGatewayProxyResponse{
		Body:       err.Error(),
		StatusCode: statusCode,
	}, err
}

```

```infra.go
package main

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
)

type db struct {
	ddb *dynamodb.DynamoDB
}

func newDynamoDB() (*db, error) {
	sess, err := session.NewSession()
	if err != nil {
		return nil, err
	}
	return &db{ddb: dynamodb.New(sess)}, nil
}

func (db *db) putAmazonURL(url string) error {
	input := &dynamodb.PutItemInput{
		TableName: aws.String("table"),
		Item: map[string]*dynamodb.AttributeValue{
			"id": {
				N: aws.String(getRandID()),
			},
			"time": {
				N: aws.String(getEpochTime()),
			},
			"url": {
				S: aws.String(url),
			},
		},
	}
	if _, err := db.ddb.PutItem(input); err != nil {
		return err
	}
	return nil
}
```

### コンパイルしてZIPにする

M1 Macを使っているため、`GOARCH=amd64 GOOS=linux`の指定しています。
```
GOARCH=amd64 GOOS=linux go build -o handler *.go
```
実行ファイルをzipに圧縮します。
```
zip function.zip handler
```

### zipのアップロードとランタイム設定変更

コードソースから`アップロード元` `zipファイル`を選択してアップロードします。
![Lambda2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1391445/3a1a1df1-7b6f-233f-dc4e-7f21c3eb1d10.png)

ハンドラの名前を初期の`hello`から`handler`に変更します。
![Lambda3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1391445/060e3103-ec53-e22f-5149-59309ccc128f.png)

### DynamoDBの作成とLambdaに実行ロール追加

設定から`ロール名` `ロールの許可ポリシー` までいったら、
`ポリシーの編集` を選択してビジュアルエディタで`DynamoDB` `PutItem`のアクションを追加します。
![Lambda5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1391445/d2348439-06d4-e49e-ea4a-0463c051e82d.png)
![Lambda6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1391445/bac83d2a-9509-fcb5-1f2b-1c9c6d9a3961.png)

### DynamoDBのテーブルを作成

DynamoDBのテーブルを作成します。
![DynamoDB.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1391445/2dbea49e-843e-0e38-89be-d6fe7122dbdc.png)

:::note warn
パーティションキーとソートキーが数値となっている点に注意してください！
:::

### Lambda + DynamoDBの連携をテスト

テストのイベント作成して、テンプレートから`apigateway-aws-proxy`を選択してイベント JSONのbodyを変更します。
```
  "body": "{\"url\": \"https://www.amazon.co.jp\"}",
```
![Lambda4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1391445/e934c204-1b7f-549d-d75d-913bc8e4d276.png)
テストを実行して成功が返ってきたらOKです。

### API Gatewayとの連携

API GatewayのAPIを作成します。
`REST API` `新しいAPI`を選択します。
![apigateway1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1391445/bf154bcc-36df-fd7f-78d4-30b07270d74b.png)

`アクションタブ`から`メソッドの作成` `POST`を選択します。
![apigateway2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1391445/8d580a4b-67bb-ca13-bf4d-3940932671c4.png)

統合タイプをLambda関数にして、`Lambdaプロキシ統合の使用`にチェックを入れます。
![apigateway3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1391445/c50b322c-896f-23ef-5fa6-26f49ff9eca3.png)

:::note warn
チェックを入れることを忘れずに！！
:::

### API Gateway + Lambda + DynamoDBの連携をテスト

テスト本文にこちらを入力して実行します。
```
{
    "url": "https://www.amazon.co.jp"
}
```

成功が帰ってきたらOK。お疲れ様でした。

# 最後に

次回はSAMで環境構築をコード化する、テストコードのパターンを増やす、追加したURLを取得するAPIを追加、などやる予定です。