【CDK】 色んな言語でCRUD API作る【Go】

# この記事について
[AWS CDKv2](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-construct-library.html)でCRUD操作ができるAPIを作る
今回はGoを使う

# プロジェクト初期化
## CLIのインストール
CLI自体はnodeのものを使う(ので導入済みならスキップ)

```bash
npm install -g aws-cdk
cdk --version
```

バージョンが表示されればOK

## CDK Toolkitのデプロイ
初回のみCDKを利用する際に必要となるファイル群のデプロイが必要
→これもやったことがあるならスキップ

```bash
cdk bootstrap aws://${ACCOUNT}/${REGION}
```

以下のように表示されればOK

```bash
✅  Environment aws://${ACCOUNT}/${REGION} bootstrapped.
```

※異なるアカウント、リージョンでCDKを利用する場合はまたこの作業が必要になる

## プロジェクト初期化
適当なディレクトリで↓を実行

```bash
cdk init app --language go
```

## モジュールのインストール
必要なモジュールを`go.mod`に書き込む

```go.mod
module todoapp-go

go 1.16

require (
	github.com/aws/aws-cdk-go/awscdk/v2 v2.34.2
	github.com/aws/aws-cdk-go/awscdklambdagoalpha/v2 v2.34.2-alpha.0
	github.com/aws/aws-lambda-go v1.34.0
	github.com/aws/aws-sdk-go-v2 v1.16.7
	github.com/aws/aws-sdk-go-v2/config v1.15.14
	github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.9.7
	github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.9
	github.com/aws/constructs-go/constructs/v10 v10.1.58
	github.com/aws/jsii-runtime-go v1.63.0
	github.com/google/uuid v1.3.0
)
```

書いたら↓を実行して`go.sum`が作成されたら完了

```bash
go mod tidy
```

# 実装
## CDKのコードを書く

```todoapp-go.go
package main

import (
	"fmt"

	"github.com/aws/aws-cdk-go/awscdk/v2"
	"github.com/aws/aws-cdk-go/awscdk/v2/awsapigateway"
	"github.com/aws/aws-cdk-go/awscdk/v2/awsdynamodb"
	"github.com/aws/aws-cdk-go/awscdk/v2/awsiam"
	"github.com/aws/aws-cdk-go/awscdk/v2/awslogs"
	"github.com/aws/aws-cdk-go/awscdklambdagoalpha/v2"
	"github.com/aws/constructs-go/constructs/v10"
	"github.com/aws/jsii-runtime-go"
)

type TodoappGoStackProps struct {
	awscdk.StackProps
}

func NewTodoappGoStack(scope constructs.Construct, id string, props *TodoappGoStackProps) awscdk.Stack {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}
	stack := awscdk.NewStack(scope, &id, &sprops)

	// DynamoDB
	table := awsdynamodb.NewTable(stack, jsii.String("TodosTable"), &awsdynamodb.TableProps{
		TableName: jsii.String("todoapp-go-todos-table"),
		PartitionKey: &awsdynamodb.Attribute{
			Name: jsii.String("id"),
			Type: awsdynamodb.AttributeType_STRING,
		},
		RemovalPolicy: awscdk.RemovalPolicy_DESTROY,
	})

	// Lambda関数
	function := awscdklambdagoalpha.NewGoFunction(stack, jsii.String("TodoFunction"), &awscdklambdagoalpha.GoFunctionProps{
		FunctionName: jsii.String("todoapp-go-function"),
		Entry:        jsii.String("src"),
		Environment: &map[string]*string{
			"TODOS_TABLE_NAME": table.TableName(),
		},
	})
	// LambdaにDynamoDBのCRUD操作権限を付与
	function.AddToRolePolicy(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{
		Effect: awsiam.Effect_ALLOW,
		Actions: jsii.Strings(
			"dynamodb:Scan",
			"dynamodb:PutItem",
			"dynamodb:UpdateItem",
			"dynamodb:DeleteItem"),
		Resources: jsii.Strings(*table.TableArn()),
	}))

	// LogGroup
	awslogs.NewLogGroup(stack, jsii.String("TodoFunctionLogs"), &awslogs.LogGroupProps{
		LogGroupName:  jsii.String(fmt.Sprintf("/aws/lambda/%s", *function.FunctionName())),
		RemovalPolicy: awscdk.RemovalPolicy_DESTROY,
	})

	// API Gateway
	restapi := awsapigateway.NewRestApi(stack, jsii.String("TodoApi"), &awsapigateway.RestApiProps{
		RestApiName: jsii.String("todoapp-go-api"),
		DefaultCorsPreflightOptions: &awsapigateway.CorsOptions{
			AllowOrigins: awsapigateway.Cors_ALL_ORIGINS(),
			AllowMethods: awsapigateway.Cors_ALL_METHODS(),
			AllowHeaders: awsapigateway.Cors_DEFAULT_HEADERS(),
			StatusCode:   jsii.Number(200),
		},
	})
	todosResource := restapi.
		Root().
		AddResource(jsii.String("todos"), nil)
	todosResource.AddMethod(jsii.String("GET"), awsapigateway.NewLambdaIntegration(function, nil), nil)
	todosResource.AddMethod(jsii.String("POST"), awsapigateway.NewLambdaIntegration(function, nil), nil)
	todoIdResource := todosResource.AddResource(jsii.String("{id}"), nil)
	todoIdResource.AddMethod(jsii.String("PUT"), awsapigateway.NewLambdaIntegration(function, nil), nil)
	todoIdResource.AddMethod(jsii.String("DELETE"), awsapigateway.NewLambdaIntegration(function, nil), nil)

	return stack
}

func main() {
	app := awscdk.NewApp(nil)

	NewTodoappGoStack(app, "TodoappGoStack", &TodoappGoStackProps{
		awscdk.StackProps{
			Env: env(),
		},
	})

	app.Synth(nil)
}

func env() *awscdk.Environment {
	return nil
}
```

例によって例のごとく今回もLambdaはGo言語専用のモジュール`awscdklambdagoalpha`を使っている
こちらについて少し補足↓

### awscdklambdagoalphaについて

```python
function := awscdklambdagoalpha.NewGoFunction(stack, jsii.String("TodoFunction"), &awscdklambdagoalpha.GoFunctionProps{
	FunctionName: jsii.String("todoapp-go-function"),
	Entry:        jsii.String("src"),
	Environment: &map[string]*string{
		"TODOS_TABLE_NAME": table.TableName(),
	},
})
```

**Entry**にLambdaのコードを置いてあるディレクトリを指定すればOK
また、エントリーポイントとなるファイルの名前は**main.go**でなければならない点に注意

```
todoapp-go
 ├-go.mod
 ├-go.sum
 ├-todoapp-go.go
 └-src
   └-main.go ←Lambdaのエントリーポイント
```

## Lambdaのコード実装
[こちら](https://github.com/tetsu0000/todoapp-go)のサンプルコードを参照
Goはほぼ使った経験がないのであまり参考にならないかもしれない(特にメソッドやパスに応じたルーティングとか)

# デプロイ

↓を実行

```bash
cdk deploy TodoappGoStack
```

# 動作確認

↓を実行

```bash
export ENDPOINT="API Gatewayのエンドポイント"
bash tests/test.bash
```

# 参考リンク

サンプルコード
https://github.com/tetsu0000/todoapp-go