【AWS SDK for JavaScript v3】DynamoDB操作のあれこれをTypeScriptで理解する
この記事で解決すること
SDK for JavaScript v3を用いてDynamoDBを操作する時に使用するライブラリの違い
-
@aws-sdk/client-dynamodb
: DynamoDBClient -
@aws-sdk/lib-dynamodb
: DynamoDBDocumentClient
やりたいこと
SDK for JavaScript v3を用いてDynamoDBにデータを挿入するLambda関数コードを書きます。
環境
Lambda Node.js 20
APIGateway, DynamoDB
(AWS SAMによるデプロイ、APIGatewayコンソール上からテスト)
AWS SDK for JavaScript v3でのDynamoDB操作について
AWS SDK for JavaScript v3では、操作したい各リソースごとにライブラリをインストールして使用します。DynamoDB操作の場合、@aws-sdk/client-dynamodb
もしくは@aws-sdk/lib-dynamodb
のどちらかをインストールします。
npm install @aws-sdk/client-dynamodb
または
npm install @aws-sdk/lib-dynamodb
ここで謎
選択肢が二つ。どっちでも良さそうに見える。
どう違うの?どっちを使えばいいの??
どう違うの?(ざっくり)
@aws-sdk/client-dynamodb
SDKにおいて、操作可能な各サービスのクライアントライブラリのパッケージ名称は@aws-sdk/client-xxxxx
で始まっています。
@aws-sdk/lib-dynamodb
一方で、@aws-sdk/lib-xxxxx
という名称のパッケージ(ライブラリ)があるサービスはDynamoDBとS3のみです。(2024/04/15時点)
例えば@aws-sdk/lib-dynamodb
についてはドキュメントに以下のような記載があります。
The document client simplifies working with items in Amazon DynamoDB by abstracting away the notion of attribute values. This abstraction annotates native JavaScript types supplied as input parameters, as well as converts annotated response data to native JavaScript types.
@aws-sdk/client-dynamodb
と異なり@aws-sdk/lib-dynamodb
はDynamoDBの属性値の概念を抽象化することで操作を簡素化したものだそうです。
DynamoDBが返すアイテムのデータ型はAttributeValue
ですが、ドキュメントクライアントにより JavaScriptの型を使用してリクエスト・レスポンスを扱うことができます。
ドキュメントクライアントはDynamoDBClientをラップしたものと言えるでしょう。
分かりづらいので、具体的なコードを見てみましょう。
コードの違いを見る
やること
APIGatewayとLambdaでAPIを実装し、id
をPKとするシンプルなDynamoDBテーブルに以下のアイテムをputします。
id | name | age |
---|---|---|
1 | John | 20 |
テーブルとAPIは作成済みであることを前提とし、以降はLambda関数のコードについてのみ説明します。
@aws-sdk/client-dynamodb
の場合
import { DynamoDBClient, PutItemCommandInput, PutItemCommand } from '@aws-sdk/client-dynamodb';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
const dynamodbClient = new DynamoDBClient({
region: 'ap-northeast-1',
});
export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
const params: PutItemCommandInput = {
TableName: 'test-table',
Item: {
id: { S: '1' },
name: { S: 'John' },
age: { N: '20' },
},
};
const command = new PutItemCommand(params);
try {
await dynamodbClient.send(command);
return {
statusCode: 200,
body: 'success',
};
} catch (err) {
return {
statusCode: 500,
body: JSON.stringify({
message: 'some error happened',
}),
};
}
};
@aws-sdk/lib-dynamodb
の場合
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, PutCommand, PutCommandInput } from '@aws-sdk/lib-dynamodb';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
const dynamodbClient = new DynamoDBClient({
region: 'ap-northeast-1',
});
// ここで、↑で初期化したDynamoDBClientを用いてDynamoDBDocumentClientを初期化
const docClient = DynamoDBDocumentClient.from(dynamodbClient);
export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
const params: PutCommandInput = {
TableName: 'test-table',
Item: {
id: '1', // ここが違う!
name: 'John', // ここが違う!
age: 20, // ここが違う!
},
};
const command = new PutCommand(params);
try {
await docClient.send(command);
return {
statusCode: 200,
body: 'success',
};
} catch (err) {
return {
statusCode: 500,
body: JSON.stringify({
message: 'some error happened',
}),
};
}
};
何が違う?
-
@aws-sdk/lib-dynamodb
を使うと@aws-sdk/client-dynamodb
の DynamoDBDocumentを用いて初期化した DynamoDBDocumentClientを使用することが、まず一つ目の違いです。 -
そして大きく異なるのは、上記コードの
params
の書き方です。
@aws-sdk/lib-dynamodb
の DynamoDBDocumentClientでは、putするItem
を単純なkeyとvalueで定義しています。
これを@aws-sdk/client-dynamodb
の DynamoDBClientで同じように書いてみましょう。(id: { S: '1' }
をid: '1'
とする)
client-dynamodbのみでlib-dynamodbの書き方をする
import { DynamoDBClient, PutItemCommandInput, PutItemCommand } from '@aws-sdk/client-dynamodb';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
const dynamodbClient = new DynamoDBClient({
region: 'ap-northeast-1',
});
export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
const params: PutItemCommandInput = {
TableName: 'test-table',
Item: {
id: '1', // { S: '1' }ではなく'1'とする
name: 'John',
age: '20',
},
};
const command = new PutItemCommand(params);
try {
await dynamodbClient.send(command);
return {
statusCode: 200,
body: 'success',
};
} catch (err) {
return {
statusCode: 500,
body: JSON.stringify({
message: 'some error happened',
}),
};
}
};
するとItem
内のプロパティに以下のような型エラーが発生しました。
Type 'string' is not assignable to type 'AttributeValue'. ts(2322)
'1'
というstring型のままだとDynamoDBClientのPutItemCommandInput
においてDynamoDBレコードのデータ値の型 AttributeValueには適用できないのです。
つまり
@aws-sdk/lib-dynamodb
の DynamoDBDocumentClientを使うと、DynamoDBレコードのAttributeValueに従って厳密に記述せずにJavaScriptで取り扱うことのできるstringやnumberなどの型で記述することができます。
どっちを使う?
@aws-sdk/lib-dynamodb
の DynamoDBDocumentClientはJavaScriptの型を用いてシンプルに記述することができます。
コードの記述量が減るので開発を進めやすくなります。小規模だったり簡単な操作のみであれば DynamoDBDocumentClientを使うとよさそうです。
@aws-sdk/client-dynamodb
の DynamoDBClientはDynamoDBのAPIに直接アクセスすることで様々な操作が可能です。大量のデータを扱ったり、複雑な操作をしたりする場合(@aws-sdk/lib-dynamodb
では対応していない操作がある場合)は DynamoDBClient を使うことになります。
開発規模や状況に応じて、方針を決めて使い分けるとよいでしょう。
次に調べたいこと
今回はDynamoDBのデータ型やマーシャリング、アンマーシャリング等の深掘りはせずに簡単な処理の記述方法の違いについて書きました。
TypeScriptを用いてLambda関数コードを書くにあたり、データ型についてもさらに調べてみたいと思いました。
Discussion