DynamoDBから値を取ってきて返すAPIを作成するにあたり、Lambdaを通さずに直接DynamoDBから値を取れることを知ったので試してみました。
作るもの
API GatewayからLambdaを通さずにDynamoDBに直接アクセスし、API Gateway + DynamoDBでgetItemするAPIを作成します。
今回は、GET books/{title}
を叩くと以下のような形でDynamoDBに保存されている値を整形して返却してくれるようなAPIを作成していきます。
{
"title": "how_to_book",
"price": 1000
}
構成
環境
cdk --version
2.10.0 (build e5b301f)
tsc --v
Version 3.9.10
完成系
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as iam from 'aws-cdk-lib/aws-iam';
export class TestBookStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
// DynamoDB
const table = new dynamodb.Table(this, "dynamoDB", {
tableName: "Book",
partitionKey: { name: 'title', type: dynamodb.AttributeType.STRING }
});
// API Gateway
const api = new apigateway.RestApi(this, "bookApi", {
deployOptions: {
stageName: "dev",
metricsEnabled: true,
dataTraceEnabled: true,
}
});
// api -> DynamoDBのIAMポリシー
const credentialRole = new iam.Role(this, 'role', {
assumedBy: new iam.ServicePrincipal("apigateway.amazonaws.com"),
});
credentialRole.attachInlinePolicy(
new iam.Policy(this, 'getItemPolicy', {
statements: [
new iam.PolicyStatement({
actions: [
"dynamodb:GetItem"
],
effect: iam.Effect.ALLOW,
resources: [table.tableArn]
})
]
})
);
// apiのリソースの用意
const allResources = api.root.addResource('books');
const bookResource = allResources.addResource('{title}');
// titleを指定してitemを1件取得するapi
bookResource.addMethod('GET', new apigateway.AwsIntegration({
service: 'dynamodb',
action: 'GetItem',
options: {
credentialsRole: credentialRole,
requestTemplates: {
'application/json': `{
"Key": {
"title": {
"S": "$input.params('title')"
}
},
"TableName": "Book"
}`
},
integrationResponses: [
{
statusCode: '200',
responseTemplates: {
'application/json': `{
"title": "$input.path('$').Item.title.S",
"price": $input.path('$').Item.price.N,
}`
}
}
]
}
}),
{
methodResponses: [
{
statusCode: '200',
}
]
});
}
}
DynamoDB
まずはDynamoDBを作成します。
Book
という名前のテーブルを作成し、title
という名前のパーティションキーを用意します。
const table = new dynamodb.Table(this, "dynamoDB", {
tableName: "Book",
partitionKey: { name: 'title', type: dynamodb.AttributeType.STRING },
});
API Gateway
次にAPI Gatewayを作成します。
少し長くなるので、小分けに説明を書いていきます。
RestApi
REST APIで実装をしていくので、まずはそれを記述します。
const api = new apigateway.RestApi(this, "bookApi", {
deployOptions: {
stageName: "dev",
metricsEnabled: true,
dataTraceEnabled: true
}
});
addResource
今回はGET books/{title}
といった形のAPIを作成したいので、それを記述します。
const allResources = api.root.addResource('books');
const bookResource = allResources.addResource('{title}');
このように書くと、以下のようなかたちでリソースが用意されます。
addMethod
DynamoDBにGetItemをしに行くAPIなので、それを記述します。
統合タイプにAWSサービス
、サービスにdynamodb
を指定することで、API Gatewayから直接DynamoDBへアクセスすることができます。
bookResource.addMethod('GET', new apigateway.AwsIntegration({
service: 'dynamodb',
action: 'GetItem'
}));
ちなみに、全件取得したい場合には以下のような書き方になります。
allResources.addMethod('GET', new apigateway.AwsIntegration({
service: 'dynamodb',
action: 'Scan'
}));
統合リクエスト
統合リクエストを利用し、バックエンド(DynamoDB)に情報を渡す際にどのような変換を行うのか定義していきます。
今回は、クライアントからのリクエストをDynamoDBのGetItem APIリクエストに変換します。
$input.params('title')
と書くことで、GET books/{title}
の{title}
の部分を取得することができます。
これを利用し、DynamoDBのBook
というテーブルから、パーティションキーであるtitle
の値を指定してGetItem
します。
requestTemplates: {
'application/json': `{
"Key": {
"title": {
"S": "$input.params('title')"
}
},
"TableName": "Book"
}`
}
全件取得ではこのように書くことができます。
requestTemplates: {
'application/json': `{
"TableName": "Book"
}`
}
統合レスポンス
今回は、DynamoDB からのレスポンスをそのままクライアントに返すのではなく、整形してから返していきます。
そのために統合レスポンスを利用し、DynamoDBから返ってきた情報の変換をしていきます。
$input.path('$')
にDynamoDBから取ってきた値が入ってくるため、それを利用して整形を行います。
integrationResponses: [
{
statusCode: '200',
responseTemplates: {
'application/json': `{
"title": "$input.path('$').Item.title.S",
"price": $input.path('$').Item.price.N,
}`
}
}
]
メソッドレスポンス
今回は正常終了パターンのみ設定しておきます。
methodResponses: [
{
statusCode: '200',
}
]
IAMポリシー
このままではAPI GatewayにDynamoDBを触る権限がないため、ロールを作成しアタッチします。
DynamoDBにGetItemする許可をするIAMポリシーを作成し、ロールに紐付けます。
全件取得したい場合には、actions
に"dynamodb:Scan"
を設定します。
const credentialRole = new iam.Role(this, 'role', {
assumedBy: new iam.ServicePrincipal("apigateway.amazonaws.com"),
});
credentialRole.attachInlinePolicy(
new iam.Policy(this, 'getItemPolicy', {
statements: [
new iam.PolicyStatement({
actions: [
"dynamodb:GetItem"
],
effect: iam.Effect.ALLOW,
resources: [table.tableArn]
})
]
})
);
上記で用意したロールを以下のようにしてアタッチします。
bookResource.addMethod('GET', new apigateway.AwsIntegration({
service: 'dynamodb',
action: 'GetItem',
options: {
credentialsRole: credentialRole,
}
}));
動作確認
まずは下準備として、スタックのデプロイを行い、作成したDynamoDBにデータを入れておきます。
データの準備ができたら、実際にAPIを叩いてみたいと思います。
今回はコンソール上から実施していきます。
API Gatewayのテスト呼び出しの画面を表示し、先程用意したデータに存在するtitleを{title}の部分に入力してテストボタンを押下します。
まとめ
API GatewayからLambdaを通さずにDynamoDBに直接アクセスし、API Gateway + DynamoDBでgetItemするAPIを作成しました。
複雑な変換などを行いたい場合はLambdaを利用した方が有効な場合もありますが、今回のような単純なものであればAPI Gateway + DynamoDBの構成で充分だと感じました。
参考資料
https://qiita.com/ma2shita/items/351746f2b6c414f9d5ab
https://dev.to/elthrasher/aws-cdk-api-gateway-service-integration-with-dynamodb-2ek0