echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【AWS】AWS SDK for JavaScript v3でLambdaを書く(DynamoDB編)

今回の内容

  • AWS LambdaをAWS SDK for JavaScript v3(以下「v3」と記載)で書く
    • 現在メジャー(だと思う)のはAWS SDK for JavaScript v2(以下「v2」と記載)
  • サンプルとして、DynamoDBのデータを取得するコードを書く

参考サイト

v3について&v2と違う点

大きく変わったのは下記2点だと思います。

各リソースごとに個別にインストールする

v3は各リソース(DynamoDB, S3など)のソースがモジュール単位で独立しています。(v2は全リソースのソースが単一パッケージに入っている)

そのため、例えば「S3のみ扱いたい」ような場合に、S3のモジュールのみ個別にインストールする形になりました。

APIの変更

(実際のソースは後述しますが)API(の実施方法)がv2とは完全に別物です。

v3では、どのリソースも基本的に下記の流れでAPIを実施します。

  1. 各リソースのクライアントを定義する
  2. 各リソースの操作コマンド(=これがv2のAPI関数に相当)を定義する
  3. 1のクライアントのsendメソッドで、2のコマンドを送信する
  4. 3の結果を取得する。(これがAPIの戻り値に相当)

上記を見てもわかる通り、v2とは完全に別物(≒破壊的変更クラス)なので、最初は戸惑うかもしれません。

v3のメリット・デメリット

メリット:サイズが小さくなる

先述の通り、v3ではリソース単位で個別にインストールします。
そのため、不要なリソースのモジュールが入らない分、最終的なサイズがv2より少なくなります。

デメリット1:LambdaのNode.jsランタイムに入っていない

v3はAWS LambdaのNode.jsランタイムにプレインストールされていません。(v2はされている)
そのため、v2とは違いAWS上で実行する場合、何らかの方法でデプロイパッケージに含める必要があります。(webpackなりLambda Layerなり)

先程メリットで「サイズが小さくなる」と書きましたが、上記の理由により、AWS上で動かす場合はむしろ最終的なサイズはv2に比べて増えてしまいます。

どっちとも言えない1:APIが完全に別物

v2とはソースが完全に別物なので、最初は戸惑ったり毛嫌いしてしまうかもしれません。
が、こればっかりは仕方がないので、慣れるしかない...というのが正直な感想です。

今回自分もv3のソースを書きましたが、実際v3の書き方でもそこまで不便さや書きにくさは感じなかったので、多分毛嫌いさえしなければ時間と経験が解決してくれるんではないかと思います。

どっちとも言えない2:情報が少ない

まだまだv2の方がメジャー(だと思う)ので、情報量は圧倒的にv2の方が多いです。

ただ、それでもv3の有益な情報はたくさんありますし、なにより公式の情報が結構充実しているので、そこまで情報が不足している...とまでは感じませんでした。

前提:今回使用するAPI

今回使用するAPIですが、過去にも出てきた、自作の「ドルアーガの塔 宝物取得API」を使用しています。
内容については「パスパラメータfloorで指定した階の宝物情報を返す」というものです。

もし気になった方は「ドルアーガの塔 宝物 一覧」などでググってみると良いかもしれません。

実際のソース

では、公式サイトの説明に沿って、ソースを書いてきます。
事前に@aws-sdk/client-dynamodbをインストールしておきます。

またTypeScriptで書く場合、合わせて@types/aws-lambdaもインストールしておくと、Lambdaトリガーが扱いやすくなります。

// AWS CDK v3のソース
import { Context, APIGatewayEvent, APIGatewayProxyResult } from 'aws-lambda';
import { DynamoDBClient, DynamoDBClientConfig, GetItemCommand, GetItemCommandInput } from '@aws-sdk/client-dynamodb';
  
const handler = async (event: APIGatewayEvent, context: Context): Promise<APIGatewayProxyResult> => {
  const floor = event.pathParameters?.floor ? Number(event.pathParameters.floor) : -1;
  
  // 1. クライアントを定義する
  // なおconfigの定義は、v2のものとだいたい同じ
  const config: DynamoDBClientConfig = {};
  const client = new DynamoDBClient(config);
  
  // 2. 操作コマンドを定義する。  
  // 今回はGetItemCommand(v2のGetItemに該当)を定義  
  // paramの定義も、v2のものとだいたい同じ
  const param:GetItemCommandInput = {
    TableName : 'tower-of-druaga',
    Key: {
      Type: {'S': 'treasure'},
      Floor: {'N': `${floor}`}, 
    },
  };
  const command = new GetItemCommand(param);
  
  // 3. 1のクライアントのsendメソッドで、2のコマンドを送信する  
  // 4. 3の結果を取得する。(これがAPIの戻り値に相当)
  const data = await client.send(command);
     
  const response: APIGatewayProxyResult = {
    statusCode: 200,
    body: JSON.stringify({
      treasures: data,
    }),
  };

  return response;
};
  
module.exports = { handler };

コメントで補足しましたが、「APIの変更」に記載した流れに沿って書きました。
今回はDynamoDBを例にしましたが、v3では他のリソースも大体この流れになります。

あと、client.send()の戻り値はPromise<T> 型なので(最初からPromise型で返ってくる)、最後に.promise()を実施しなくてよくなったのは、地味ながらv3の便利な点だと思います。

上記を実施すると、下記結果が返ります。

「$metadata」にメタデータ情報、Itemに該当データの情報がちゃんと格納されています。

{
    "$metadata": {
        "httpStatusCode": 200,
        "requestId": "QL08LADH1ME1QFG4(以下省略)",
        "attempts": 1,
        "totalRetryDelay": 0
    },
    "Item": {
        "Necessary": {
            "BOOL": false
        },
        "Floor": {
            "N": "1"
        },
        "Type": {
            "S": "treasure"
        },
        "Detail": {
            "L": [
                {
                    "M": {
                        "Condition": {
                            "S": "グリーンスライムを3匹倒す"
                        },
                        "Effect": {
                            "S": "壁を宝箱を取る前後1回ずつ壊せる"
                        },
                        "Memo": {
                            "S": ""
                        },
                        "Name": {
                            "S": "カッパーマトック"
                        }
                    }
                }
            ]
        }
    }
}

DocumentClientを使う

ただDynamoDBのデータを扱う場合、DocumentClientで扱った方が便利です。
なので、次はDocumentClientを使ってみます。

なおDocumentClientを扱う場合、追加で@aws-sdk/lib-dynamodb, および@aws-sdk/util-dynamodbが必要になるので、これもインストールしておきます。

※@aws-sdk/client-dynamodbも必要です。

ただ下記ソースの通り、DocumentClientクライアントを生成する箇所以外、基本は変わらないです。(コマンドの名前や型以外)

import { Context, APIGatewayEvent, APIGatewayProxyResult } from 'aws-lambda';
import { DynamoDBClient, DynamoDBClientConfig } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand, GetCommandInput } from '@aws-sdk/lib-dynamodb'
  
const handler = async (event: APIGatewayEvent, context: Context): Promise<APIGatewayProxyResult> => {
  const floor = event.pathParameters?.floor ? Number(event.pathParameters.floor) : -1;
  
  const config: DynamoDBClientConfig = {};
  const client = new DynamoDBClient(config);  
  
  // ここでDocumentClientクライアントを生成する。
  const dcClient = DynamoDBDocumentClient.from(client);
  
  // paramの定義も、v2のgetのものとだいたい同じ
  const param:GetCommandInput = {
    TableName : 'tower-of-druaga',
    Key: {
      Type: 'treasure',
      Floor: floor, 
    },
  };
 
  // クライアントのsendを実行するのは同じ
  const command = new GetCommand(param);
  const data = await dcClient.send(command);
  
  const response: APIGatewayProxyResult = {
    statusCode: 200,
    body: JSON.stringify({
      treasures: data.Item,
    }),
  };
  
  return response;
};
  
module.exports = { handler };
{
    "$metadata": {
        "httpStatusCode": 200,
        "requestId": "OMUB8IN7COJVLBG7(以下省略)",
        "attempts": 1,
        "totalRetryDelay": 0
    },
    "Item": {
        "Necessary": false,
        "Floor": 1,
        "Type": "treasure",
        "Detail": [
            {
                "Condition": "グリーンスライムを3匹倒す",
                "Effect": "壁を宝箱を取る前後1回ずつ壊せる",
                "Memo": "",
                "Name": "カッパーマトック"
            }
        ]
    }
}

まとめ

以上、AWS SDK for JavaScript v3でLambdaを書いてみた結果でした。

書いた感想として、とにかく慣れかな...と感じました。
ただ先述の通り、v2と比べてもそれほど不便さや使いにくさは感じませんでした。

なお、AWS公式はv3を推奨しています。
また今のところv2もサポート終了という話はありませんが、v2が登場から8年も経っているので、(v3が登場してある程度経ったこともあり)何かあってもおかしくはないかもしれません。

なので、これからはv3を作成するか、あるいはv3への移行も視野に入れた方が良いのかもしれません。

それでは、今回はこの辺で