0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CDK×Lambda×golang×Dynamoでアプリを作ってみる APIGATEWAY+スタック統合編(第三回)

Last updated at Posted at 2024-05-26

さて、第一回と第二回でLambdaとDynamoDBのスタックをそれぞれ作成しデプロイまでやりました。

ただ、このままではアクセスポイントがないため若干不便なので、APIgatewayを準備したいと思います。
また、現状ではスタックを別々に分けていますが、アプリケーション自体が大きくないので、扱いやすくなるためいっそのこと1つのスタックに統合しようと思います。

記述内容自体はほとんど変わりませんが、関数名をNewAppStackと変更しその中にNewDynamoDBStack関数とNewLambdaStack関数をまとめました。これで重複コードも削除できるのですっきりしました。

package main

import (
	"log"
	"os"
	"github.com/aws/aws-cdk-go/awscdk/v2"
	"github.com/aws/aws-cdk-go/awscdk/v2/awslambda"
	"github.com/aws/aws-cdk-go/awscdk/v2/awsapigatewayv2"
	"github.com/aws/aws-cdk-go/awscdk/v2/awsapigatewayv2integrations"
	"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 AppStackProps struct {
	awscdk.StackProps
}

func NewAppStack(scope constructs.Construct, id string, props *AppStackProps) 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("MyDynamoDB"), &awsdynamodb.TableProps{
		TableName: jsii.String("MyDynamoDB"),
		PartitionKey: &awsdynamodb.Attribute{
			Name: jsii.String("id"),
			Type: awsdynamodb.AttributeType_STRING,
		},
		BillingMode: awsdynamodb.BillingMode_PAY_PER_REQUEST,
	})

	// Lambda関数の定義
	lambdaFunction := 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),
		Environment: &map[string]*string{
			"DYNAMODB_TABLE_NAME": table.TableName(),
		},
	})

	// テーブルへの権限付与
	table.GrantReadWriteData(lambdaFunction)

	// API Gatewayの定義
	api := awsapigatewayv2.NewHttpApi(stack, jsii.String("MyApi"), &awsapigatewayv2.HttpApiProps{
		ApiName: jsii.String("MyApi"),
	})

	// Lambda関数の統合
	lambdaIntegration := awsapigatewayv2integrations.NewHttpLambdaIntegration(jsii.String("MyIntegration"), lambdaFunction, nil)
	
	// API Gatewayの定義
	api.AddRoutes(&awsapigatewayv2.AddRoutesOptions{
		Path: jsii.String("/test"),
		Methods: &[]awsapigatewayv2.HttpMethod{
			awsapigatewayv2.HttpMethod_GET,
			awsapigatewayv2.HttpMethod_POST,
			awsapigatewayv2.HttpMethod_PUT,
			awsapigatewayv2.HttpMethod_DELETE,
		},
		Integration: lambdaIntegration,
	})

	return stack
}


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

	NewAppStack(app, "AppStack", &AppStackProps{
		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),
	}
}

今回、追加した内容としては以下の2点です。

  • lambda関数に作成するテーブルへの読み書き操作権限を与えること
  • apigatewayを作成しパスを設定し、lambdaと統合すること

テーブルへの権限付与

テーブルへの権限付与については以下の箇所です。GrantWriteDataGrantReadDataなど他にも様々な権限を与えることができます。

今回は作成したlambda関数にテーブルの全ての読み書き操作を許可します。

table.GrantReadWriteData(lambdaFunction)
メソッド名 説明
GrantFullAccess DynamoDB テーブルに対するすべての操作を許可します。
GrantReadData BatchGetItem、GetRecords、GetShardIterator、Query、GetItem、Scan、DescribeTable など、すべての読み取り操作を許可します。
GrantReadWriteData BatchGetItem、GetRecords、GetShardIterator、Query、GetItem、Scan、BatchWriteItem、PutItem、UpdateItem、DeleteItem、DescribeTable など、すべての読み書き操作を許可します。
GrantWriteData BatchWriteItem、PutItem、UpdateItem、DeleteItem、DescribeTable など、すべての書き込み操作を許可します。
GrantStreamRead DescribeStream、GetRecords、GetShardIterator、ListStreams など、テーブルのストリームに対するすべての読み取り操作を許可します。
GrantStream テーブルのストリームに関連する指定されたアクションを許可します。
GrantTableListStreams 現在の DynamoDB テーブルに接続されているストリームの一覧を IAM プリンシパルに許可します。
Grant 特定のアクションのセットを IAM プリンシパルに対して許可します。
GrantCrud テーブルに対する CRUD(作成、読み取り、更新、削除)操作を許可します。
GrantManagement テーブルの管理操作(例: DescribeTable や ListTables)を許可します。

APIgateway

続いてapigatewayを追加します。

以下をimportに追加して、go mod tidyを実行しましょう。

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

次にapiを定義して、そのapiとlambda関数を統合しましょう。

api := awsapigatewayv2.NewHttpApi(stack, jsii.String("MyApi"), &awsapigatewayv2.HttpApiProps{})

この関数は、新しいHttpApiオブジェクト(API Gateway HTTP API)を作成するためのコンストラクタです。
まず、awaspigatewayv2.NewHttpApiの定義はscopeid*HttpApiProps構造体を渡します。
scopeとidはこれまでに出てきたものと同じ概念なので割愛しますが、第三引数にはlambdaやdynamoと同様に設定値を渡すことができます。今回は試しにApiNameを渡してみます。

フィールド名 説明 必須 具体的な値
ApiName *string APIの名前を指定します。指定しない場合は自動生成されます。 "MyCustomApi"
CorsPreflight *CorsPreflightOptions CORS (Cross-Origin Resource Sharing) 設定を指定します。 &awsapigatewayv2.CorsPreflightOptions{ AllowOrigins: jsii.Strings("*"), AllowMethods: jsii.Strings("GET", "POST") }
CreateDefaultStage *bool デフォルトのステージを作成するかどうかを指定します(デフォルトはtrue)。 jsii.Bool(true)
DefaultDomainMapping *DomainMappingOptions デフォルトのドメインマッピングを設定します。 &awsapigatewayv2.DomainMappingOptions{ DomainName: domainName }
DefaultIntegration IHttpRouteIntegration デフォルトの統合を設定します。 lambdaIntegration
Description *string APIの説明を設定します。 "This is my custom API"
DisableExecuteApiEndpoint *bool execute-apiエンドポイントを無効にするかどうかを指定します。 jsii.Bool(false)
FailOnWarnings *bool 警告がある場合にデプロイを失敗させるかどうかを指定します。 jsii.Bool(true)

これらの設定項目を使って、API Gateway HTTP APIの動作を細かく制御できます。具体的な値の例も含めているので、必要に応じてこれらを参考に設定してください。

Lambda関数の統合

次にAPI Gatewayのルートが呼び出されたときに、指定されたLambda関数が実行されるように設定します。

lambdaIntegration := awsapigatewayv2integrations.NewHttpLambdaIntegration(jsii.String("MyIntegration"), lambdaFunction, &awsapigatewayv2integrations.HttpLambdaIntegrationProps{})

awsapigatewayv2integrations.NewHttpLambdaIntegrationの引数には以下の関数の定義から分かる通り、一意の名前とawslambda.NewFunctionで作成したlambda関数、そして最後にHttpLambdaIntegrationPropsで設定値を渡します。

HttpLambdaIntegration.go
func NewHttpLambdaIntegration(id *string, handler awslambda.IFunction, props *HttpLambdaIntegrationProps) HttpLambdaIntegration {

パッケージは以下のawsapigatewayv2integrationsをimportして使用します。こちらもgo mod tidyしましょう。

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

HttpLambdaIntegrationPropsHttpLambdaIntegrationProps.goで定義されていますが、ParameterMappingPayloadFormatVersionだけのようです。

フィールド名 説明 必須 具体的な値
ParameterMapping ParameterMapping API GatewayのリクエストパラメータをLambda関数に渡す際のマッピングを指定します。 awsapigatewayv2.ParameterMapping{QueryStrings: map[string]string{"originalParamName": "newParamName"}}
PayloadFormatVersion PayloadFormatVersion API GatewayがLambda関数に渡すペイロードのフォーマットバージョンを指定します。 awsapigatewayv2.PayloadFormatVersion_VERSION_2_0

今回は特に必要ないですが、以下のようにマッピングの設定などをしておくと、例えば外部パラメータ名userIdが内部パラメータ名 user_idに変換されるみたいです。ヘッダーも同様。

lambdaIntegration := awsapigatewayv2integrations.NewHttpLambdaIntegration(jsii.String("MyIntegration"), lambdaFunction, &awsapigatewayv2integrations.HttpLambdaIntegrationProps{
    ParameterMapping: awsapigatewayv2.ParameterMapping{
        QueryStrings: map[string]string{
            "userId": jsii.String("user_id"),
        },
        Headers: map[string]string{
            "externalHeader": "internalHeader",
        },
    },
    PayloadFormatVersion: awsapigatewayv2.PayloadFormatVersion_VERSION_2_0,
})

まあ今回は設定しないのでnilとしておきます。とりあえずここまででapiGatewayとそれにlambda関数を統合するための定義ができました。

残すところはapi.AddRoutes(&awsapigatewayv2.AddRoutesOptionsの箇所ですね。

api.AddRoutes(&awsapigatewayv2.AddRoutesOptions{
	Path: jsii.String("/test"),
	Methods: &[]awsapigatewayv2.HttpMethod{
		awsapigatewayv2.HttpMethod_GET,
		awsapigatewayv2.HttpMethod_POST,
		awsapigatewayv2.HttpMethod_PUT,
		awsapigatewayv2.HttpMethod_DELETE,
	},
	Integration: lambdaIntegration,
})

ここでは、先ほどawsapigatewayv2.NewHttpApiで定義したapiに設定を追加していきます。

今回はAddRoutesを使用します。この関数はAddRoutesOptionsを引数にとります。

HttpApi.go
AddRoutes(options *AddRoutesOptions) *[]HttpRoute

さらにAddRoutesOptionsを見てみるとAddRoutesOptions.goに以下のように定義されています。

フィールド名 説明 必須 デフォルト
Integration HttpRouteIntegration このルートで構成される統合を指定します。 ⭕️
Path *string これらのルートが構成されるパスを指定します。 ⭕️
AuthorizationScopes *[]*string 認証に含めるOIDCスコープのリスト。これらのスコープは、ゲートウェイのデフォルトの認証スコープを上書きします。 APIで構成されている場合はデフォルトの認証スコープを使用、そうでない場合はなし
Authorizer IHttpRouteAuthorizer これらのルートに関連付けられるオーソライザー。 デフォルトのオーソライザーを使用
Methods *[]HttpMethod 構成するHTTPメソッドのリスト。 HttpMethod.ANY

今回は必須であるPathMethodsに加えて、先ほど定義したlambdaIntegrationをIntegrationに設定します。

具体的には、/testパスへのGET、POST、PUT、DELETEリクエストを指定されたLambda関数にルーティングします。この設定により、指定したパスにリクエストが来た場合、対応するLambda関数が実行されます。

さて、これで完成したのでcdk deployを実行してみましょう。
すると無事、✅ AppStack✨ Deployment time: 62.46sと表示されデプロイに成功しました。

AWSのUIで確認してみるときちんとLambdaにAPI Gatewayが統合されてトリガーとして設定されていることが分かります。
スクリーンショット 2024-05-26 23.53.31.png

DynamoDBについても以前と同様に正常に作成されています。
スクリーンショット 2024-05-26 23.57.26.png

これで/testパスへAPIを投げるとトリガーになり、lambda関数に飛ばすことができるようになりました🙌
したがって次はこのDynamoDBに読み書き権限を与えたlambda関数の中でmethodによる条件分岐でテスト処理を書いて、CRUD処理をできるように実装してみたいと思います!

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?