LoginSignup
0
0

CDK×Lambda×golang×Dynamoでアプリを作ってみる DynamoDB編(第二回)

Last updated at Posted at 2024-05-26

さて、今回は以下の記事に続けて第二弾DynamoDBのスタックをCDKで追加していく。

まずは以下をimportに追加してgo mod tidyを実行

"github.com/aws/aws-cdk-go/awscdk/v2/awsdynamodb"

さて、まずはコードの全体像からです。
今回は以前のコードの追加していますが、まあほとんど似たような箇所が多いので前回よりも理解は楽になりましたが、Dynamoの設定値があるのでそちらの方も踏まえて見ていこうと思います。追加などわかりやすいように改行や並び順が変だったりします。

ちなみに私のDynamoDBの知識は2年前にSAPを取得した時の枯れた知識と先日再度キャッチアップした時の以下の記事レベルです。ほとんど入門者です。

package main

import (
	"log"
	"os"

	"github.com/aws/aws-cdk-go/awscdk/v2"
	"github.com/aws/aws-cdk-go/awscdk/v2/awsiam"
	"github.com/aws/aws-cdk-go/awscdk/v2/awslambda"
	"github.com/aws/constructs-go/constructs/v10"
	"github.com/aws/jsii-runtime-go"
	"github.com/joho/godotenv"

	"github.com/aws/aws-cdk-go/awscdk/v2/awsdynamodb"
)

type LambdaStackProps struct {
	awscdk.StackProps
}

type DynamoDBStackProps struct{
	awscdk.StackProps
}

func NewLambdaStack(scope constructs.Construct, id string, props *LambdaStackProps) awscdk.Stack {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}

	stack := awscdk.NewStack(scope, &id, &sprops)

	// Lambda関数の定義
	awslambda.NewFunction(stack, jsii.String("MyFunction"), &awslambda.FunctionProps{
		Runtime: awslambda.Runtime_PROVIDED_AL2(),
		Handler: jsii.String("main"),                                                     // ハンドラー関数名を適切に指定
		Code:    awslambda.Code_FromAsset(jsii.String("lambda/dynamoDBHandler"), nil), // Lambdaコードのパスを指定
	})

	return stack
}

func NewDynamoDBStack(scope constructs.Construct, id string, props *DynamoDBStackProps) awscdk.Stack {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}

	stack := awscdk.NewStack(scope, &id, &sprops)

	// DynamoDBテーブルの作成
	awsdynamodb.NewTable(stack, jsii.String("MyDynamoDB"), &awsdynamodb.TableProps{
		TableName: jsii.String("MyDynamoDB"),
		PartitionKey: &awsdynamodb.Attribute{
			Name: jsii.String("id"),
			Type: awsdynamodb.AttributeType_STRING,
		},
		BillingMode: awsdynamodb.BillingMode_PAY_PER_REQUEST,
	})

	tablerole := awsiam.NewRole(stack, jsii.String("dynamodb-role"), &awsiam.RoleProps{
		AssumedBy: awsiam.NewServicePrincipal(jsii.String("appsync.amazonaws.com"), nil),
	})
	tablerole.AddManagedPolicy(awsiam.ManagedPolicy_FromAwsManagedPolicyName(jsii.String("AmazonDynamoDBFullAccess")))

	return stack
}

func main() {
	// .envファイルを読み込む
	err := godotenv.Load()
	if err != nil {
		log.Fatalf("Error loading .env file")
	}
	// スタックの入れ物を作るイメージ
	app := awscdk.NewApp(nil)

	// で、その入れ物にLambdaを配置する。この時、idは"LambdaStack"
	NewLambdaStack(app, "LambdaStack", &LambdaStackProps{
		awscdk.StackProps{
			Env: env(),
		},
	})

	NewDynamoDBStack(app, "DynamoDBStack", &DynamoDBStackProps{
		awscdk.StackProps{
			Env: env(),
		},
	})

	app.Synth(nil)
}

func env() *awscdk.Environment {
	awsAccountId := os.Getenv("AWS_ACCOUNT_ID")
	awsRegion := os.Getenv("AWS_REGION")

	return &awscdk.Environment{
		Account: jsii.String(awsAccountId),
		Region:  jsii.String(awsRegion),
	}
}

上記で今回追加した部分を抜粋すると以下の関数です。Stackやmainでの呼び出しについては前回のlambdaと同じなので割愛します。

今回は以下関数を詳しく見ていきたいと思いますが、こちらも前回と重複している箇所があるので割愛する部分もありますのでご承知おきください。

func NewDynamoDBStack(scope constructs.Construct, id string, props *DynamoDBStackProps) awscdk.Stack {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}

	stack := awscdk.NewStack(scope, &id, &sprops)

	// DynamoDBテーブルの作成
	awsdynamodb.NewTable(stack, jsii.String("MyDynamoDB"), &awsdynamodb.TableProps{
		TableName: jsii.String("MyDynamoDB"),
		PartitionKey: &awsdynamodb.Attribute{
			Name: jsii.String("id"),
			Type: awsdynamodb.AttributeType_STRING,
		},
		BillingMode: awsdynamodb.BillingMode_PAY_PER_REQUEST,
	})

	tablerole := awsiam.NewRole(stack, jsii.String("dynamodb-role"), &awsiam.RoleProps{
		AssumedBy: awsiam.NewServicePrincipal(jsii.String("appsync.amazonaws.com"), nil),
	})
	tablerole.AddManagedPolicy(awsiam.ManagedPolicy_FromAwsManagedPolicyName(jsii.String("AmazonDynamoDBFullAccess")))

	return stack
}

まず、スタックの作成ですが、こちらは以前解説していますのでそちらをご参照ください。
どのスタックを配置する場所と一意の名前、そして設定値を渡しています。今回も以前と同様に設定値にはAccountとRegionを渡しています。

*DynamoDBStackProps) awscdk.Stack {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}

	stack := awscdk.NewStack(scope, &id, &sprops)

で、ここからが以前と異なるDynamoDBの設定です。

awsdynamodb.NewTable(stack, jsii.String("MyDynamoDB"), &awsdynamodb.TableProps{
	TableName: jsii.String("MyDynamoDB"),
	PartitionKey: &awsdynamodb.Attribute{
		Name: jsii.String("id"),
		Type: awsdynamodb.AttributeType_STRING,
	},
})

以前の解説で、lambdaでは&awslambda.FunctionPropsで設定値を渡していましたが、DynamoDBでは&awsdynamodb.TablePropsを使用します。この構造体には以下の設定値があります。

色々あるけど必須なのはPartitionKeyだけみたい。他の設定値もDynamoDBの基本を理解していれば大体わかると思うので、必要に応じて適用してみるとよさそうです。

まあ、今回はCDKの確認なのでPartionKeyBillingModeをオンデマンドにしたいので、この2つだけ設定しようと思います。

フィールド名 説明 必須 デフォルト
PartitionKey *Attribute パーティションキーの属性定義。このプロパティは必須です。 ⭕️
SortKey *Attribute ソートキーの属性定義。 ソートキーなし
BillingMode BillingMode 読み取りおよび書き込みスループットの課金方法および管理方法を指定します。 PROVISIONED
ContributorInsightsEnabled *bool CloudWatch のコントリビュータインサイトを有効にするかどうか。 false
DeletionProtection *bool テーブルの削除保護を有効にするかどうか。 false
Encryption TableEncryption サーバーサイド暗号化を有効にするかどうか。 DynamoDB によって管理される暗号化キー
EncryptionKey awskms.IKey テーブルの暗号化に使用する外部の KMS キー。Encryption が CUSTOMER_MANAGED の場合にのみ設定可能です。
ImportSource *ImportSourceSpecification S3 バケットからテーブルへのデータのインポートに関するプロパティ。 インポートなし
PointInTimeRecovery *bool ポイントインタイムリカバリを有効にするかどうか。 false
ReadCapacity *float64 テーブルの読み取り容量。BillingMode が PROVISIONED の場合にのみ設定可能です。 5
RemovalPolicy awscdk.RemovalPolicy DynamoDB テーブルに適用する削除ポリシー。 RETAIN
ReplicationRegions *[]*string レプリカテーブルを作成するリージョン。 レプリカなし
ReplicationTimeout awscdk.Duration 単一リージョンでのテーブルレプリケーション操作のタイムアウト。 30分
Stream StreamViewType テーブルのストリームに書き込まれる情報の種類を決定します。 ストリームなし
TableClass TableClass テーブルクラスを指定します。 STANDARD
TimeToLiveAttribute *string TTL 属性の名前。 TTL 無効
WaitForReplicationToFinish *bool CloudFormation スタックがレプリケーションの完了を待つかどうか。 true

ちなみに話が脱線しますがGSIやLSIの設定はどうやるんだ?と疑問に思っていたら、以下のように作成したtableにAddLocalSecondaryIndexで別途設定するっぽいです。いっそのことTablePropsに入れてくれたらいいのに。。。

    // ローカルセカンダリインデックスの追加
    table.AddLocalSecondaryIndex(&awsdynamodb.LocalSecondaryIndexProps{
        IndexName: jsii.String("LSI-Index"),
        SortKey: &awsdynamodb.Attribute{
            Name: jsii.String("LSI-SortKey"),
            Type: awsdynamodb.AttributeType_STRING,
        },
        ProjectionType: awsdynamodb.ProjectionType_ALL,
    })

    // グローバルセカンダリインデックスの追加
    table.AddGlobalSecondaryIndex(&awsdynamodb.GlobalSecondaryIndexProps{
        IndexName: jsii.String("GSI-Index"),
        PartitionKey: &awsdynamodb.Attribute{
            Name: jsii.String("GSI-PartitionKey"),
            Type: awsdynamodb.AttributeType_STRING,
        },
        SortKey: &awsdynamodb.Attribute{
            Name: jsii.String("GSI-SortKey"),
            Type: awsdynamodb.AttributeType_STRING,
        },
        ProjectionType: awsdynamodb.ProjectionType_ALL,
    })

さて、話を本筋に戻しましょう。
まず作成したstackにMyDynamoDBという名前をつけます。
そして、上記説明の通り、設定したい値をawsdynamodb.TablePropsで付与していきます。

awsdynamodb.NewTable(stack, jsii.String("MyDynamoDB"), &awsdynamodb.TableProps{
	TableName: jsii.String("MyDynamoDB"),
	PartitionKey: &awsdynamodb.Attribute{
		Name: jsii.String("id"),
		Type: awsdynamodb.AttributeType_STRING,
	},
	BillingMode: awsdynamodb.BillingMode_PAY_PER_REQUEST,
})

まずPartitionKeyをどれにするか決めます。
&awsdynamodb.Attributeでは以下のようにNameTypeを設定できることがわかります。
Nameはカラムの名前、Typeは型ですね。(正確にはAttributeの名前かな。)
ちなみに、PKとSKの型には文字列、数値、またはバイナリのみ使用可能なので、このうちのいずれかを指定するように注意です。

今回は文字列id属性をPKに指定します。

Attribute.go
type Attribute struct {
	// The name of an attribute.
	Name *string `field:"required" json:"name" yaml:"name"`
	// The data type of an attribute.
	Type AttributeType `field:"required" json:"type" yaml:"type"`
}

次にBillingModeを設定します。デフォルトではプロビジョンドになるので、大量に使用する場合は割高になりますが、今回はほとんど使用しないためオンデマンドであるawsdynamodb.BillingMode_PAY_PER_REQUESTと設定します。

とりあえず、Dynamoのテーブル設定自体はこれで完了です。
では、以下コマンドを実行しましょう。今回はスタックを2つ作成しているのでcdk deployだとエラーが出てしまいます。

cdk synth
cdk deploy --all

スクリーンショット 2024-05-25 23.34.25.png

ポリシーやロールの追加

今回はテーブルを試しに作成しただけなので細かい設定はしていませんが、本来であれば削除保護やロールなどを付与するかと思います。

その時はawscdk.NewPolicyawscdk.NewRoleとして必要な情報を渡せば設定できるみたいです。

ポリシーの追加

stackPolicy := awscdk.NewPolicy(stack, jsii.String("MyStackPolicy"), &awscdk.PolicyProps{
	Statements: []awscdk.PolicyStatement{
		awscdk.NewPolicyStatement(&awscdk.PolicyStatementProps{
			Effect:    awscdk.Effect_DENY,
			Actions:   []string{"dynamodb:DeleteTable"},
			Resources: []string{table.TableArn()},
		}),
	},
})

stack.AddPolicy(stackPolicy)

PolicyPropsでは以下のような項目を設定できる模様。

フィールド名 説明 必須 デフォルト
Document PolicyDocument このポリシーに使用する初期のPolicyDocumentを指定します。 空のポリシー
force boolean AWS::IAM::Policyの作成を強制します。 false
PolicyName *string ポリシーの名前を指定します。 ポリシーリソースの論理ID(スタック内で一意)
Statements *[]awsiam.PolicyStatement ポリシーに含まれるポリシーステートメントのリストを指定します。 なし
Users *[]awsiam.IUser ポリシーを適用するユーザーのリストを指定します。 なし
Roles *[]awsiam.IRole ポリシーを適用するロールのリストを指定します。 なし
Groups *[]awsiam.IGroup ポリシーを適用するグループのリストを指定します。 なし

ロールの追加

ロールの追加の場合は"github.com/aws/aws-cdk-go/awscdk/v2/awsiam"でパッケージが必要となるので注意。

tablerole := awsiam.NewRole(stack, jsii.String("dynamodb-role"), &awsiam.RoleProps{
	AssumedBy: awsiam.NewServicePrincipal(jsii.String("appsync.amazonaws.com"), nil),
})
tablerole.AddManagedPolicy(awsiam.ManagedPolicy_FromAwsManagedPolicyName(jsii.String("AmazonDynamoDBFullAccess")))

RolePropsでは以下のような項目を設定できる模様。
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_iam.RoleProps.html

フィールド名 説明 必須 デフォルト
assumedBy IPrincipal このロールを引き受けることができるIAMプリンシパル(例:new ServicePrincipal('sns.amazonaws.com'))。 ⭕️
description string ロールの説明。
externalIds string[] このロールを引き受ける際に提供する必要があるIDのリスト。
inlinePolicies { [string]: PolicyDocument } このロールにインライン化する名前付きポリシーのリスト。
managedPolicies IManagedPolicy[] このロールに関連付けられた管理ポリシーのリスト。
maxSessionDuration Duration 指定されたロールの最大セッション期間。
path string このロールに関連付けられたパス。
permissionsBoundary IManagedPolicy IAMエンティティ(ユーザーまたはロール)のための許可境界をサポートします。
roleName string IAMロールの名前。

まとめ

今回は前回作成したlambdaにdynamoを追加しました。
大枠を理解していればあとは設定項目の書き方を調べるだけという感じですね。

では、ここまででDynamoとLambdaの基本的な構成が完成したので次回は実際にlambda関数をgolangで記述してDynamoに書き込んだり読み込んだりしていきたいと思います!

その後は、色々と他の方のcdkの実装やドキュメントを見てみて、設定項目などで必要そうなものを追加していく予定です〜。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0