メインコンテンツまでスキップ

AWS SDK for JavaScript v3 への移行

箕浦
箕浦
開発二部

こんにちは。開発 2 部 箕浦です。
サーバーレスの開発には欠かせないAWS Lambdaですが、ランタイムバージョンのサポートが切れると、
AWSはセキュリティパッチ等を適用しなくなり、サポート切れの関数はテクニカルサポートの対象ではなくなります。
さらに、新規作成や更新もできなくなるので、ランタイムのバージョンアップが必要になってきます。

今回はとある案件で、構築時はNode.js 16.xで作成していたLambda関数でしたが、
Node.js 16.x の 2024 年 6 月 12 日のサポート終了に伴い、Node.js 20.x へ移行いたしました。
その際、AWS SDKのバージョンもv2からv3へ移行する必要がありました。

v3自体は、Node.js 18.x から登場し、利用可能だったようですが、16のまま放置していたので、今回初めて移行を試みました。
今更な情報かもしれませんが、その際、いろいろソースコードを変更する箇所がありましたので、一部紹介します。

今回SDKにて利用しているサービスは、Lambda、S3、Cognito、DynamoDB、SES でしたのでこれらをピックアップします。

ポイントとしては、

  • ①インポート
  • ②呼び出し
  • ③エラーハンドリング

の3か所です。

インポート

従来は以下のように aws-sdk 自体をインポートして、それぞれのサービスのクライアントを生成する必要がありました。

aws-sdk v2
const aws = require('aws-sdk');
const documentClient = new aws.DynamoDB.DocumentClient();

v3 では以下のように個別のモジュールをインポートする必要があります。 面倒ですが、SDKをまるごとインポートするよりも個別にインポートした方がサイズを削減することが可能です。

aws-sdk v3
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const { DynamoDBDocumentClient, DeleteCommand } = require("@aws-sdk/lib-dynamodb");
const documentClient = DynamoDBDocumentClient.from(new DynamoDBClient({ region: 'ap-northeast-1' }));

cognito, lambda, s3, ses の場合は、

aws-sdk v3
const {
CognitoIdentityProviderClient,
AdminCreateUserCommand,
AdminDeleteUserCommand,
} = require("@aws-sdk/client-cognito-identity-provider");
const { LambdaClient, InvokeCommand } = require("@aws-sdk/client-lambda");
const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
const { SESClient, SendEmailCommand } = require("@aws-sdk/client-ses");
const cognito = new CognitoIdentityProviderClient({});
const lambda = new LambdaClient({});
const s3 = new S3Client({});
const ses = new SESClient({ region: 'ap-northeast-1' });

このような感じです。

呼び出し

v2では、各サービスのAPIの呼び出し方は、一般的には クライアントが提供しているAPIに対応しているメソッドにパラメータを直接渡す方法でした。

aws-sdk v2
    const params = {
TableName: TABLE_NAME,
Key: {
UserName: user_id,
},
};
await documentClient.delete(params).promise();

v3 では、APIに対応しているCommandにパラメータを渡してコマンドを生成して、 それをクライアントのsendメソッドに渡してやります。

aws-sdk v3
    const params = {
TableName: TABLE_NAME,
Key: {
UserName: user_id,
},
};
await documentClient.send(new DeleteCommand(params));

さらにLambdaの場合は、戻り値のPayloadのParseの部分にも修正が必要でした

aws-sdk v2
    const ret = await lambda.invoke(params).promise();
const result = JSON.parse(ret.Payload);
aws-sdk v3
    const ret = await lambda.send(new InvokeCommand(params));
const result = JSON.parse(Buffer.from(ret.Payload).toString());

エラーハンドリング

v2 では、以下のように、例外が発生した際に、catchに渡される例外オブジェクトのcodeメンバを参照することで、
エラーハンドリングが可能でしたが、v3ではこの記述はだめでした。

aws-sdk v2
    try {
const ret = await cognito.send(new AdminCreateUserCommand(params));
} catch (e) {
if (e.code === 'UsernameExistsException') {
response.statusCode = 400;
body.resultCode = 'E0007';
} else if (e.code === 'InvalidPasswordException') {
response.statusCode = 400;
body.resultCode = 'E0016';
} else {
response.statusCode = 500;
body.resultCode = 'E0001';
}
response.body = JSON.stringify(body);
callback(null, response);
return;
}

v3では以下のように、instanceof を使ってハンドリングする必要がありました。

aws-sdk v3
    try {
const ret = await cognito.send(new AdminCreateUserCommand(params));
} catch (e) {
if (e instanceof UsernameExistsException) {
response.statusCode = 400;
body.resultCode = 'E0007';
} else if (e instanceof InvalidPasswordException) {
response.statusCode = 400;
body.resultCode = 'E0016';
} else {
response.statusCode = 500;
body.resultCode = 'E0001';
}
response.body = JSON.stringify(body);
callback(null, response);
return;
}

今回取り上げたサービスはほんの一部ですが、それ以外のサービスでは他にも変更点はある可能性はあります。
もし、同じ境遇の方いましたら、ご参考までに。