AWS CDK Workshop(TypeScript)をNode.js 20.xでやりたい場合

## 背景
・AWS CDK Workshop(TypeScriptワークショップ)に取り組んでいる中で、Lambdaのランタイム(Node.js)のバージョンによる差異がありコードの修正が必要だったのでその備忘です
・ワークショップはNode.js 14.xになっていますが、現在Lambdaでは14.xはサポートされていないため20.xにしたところエラーが発生し上手くいきませんでした

https://cdkworkshop.com/ja/


## 対象箇所
Node.js 20.xでやりたい場合にコードを修正する必要があった作業箇所です

https://cdkworkshop.com/ja/20-typescript/40-hit-counter/200-handler.html

```lambda/hitcounter.js
const { DynamoDB, Lambda } = require('aws-sdk');

exports.handler = async function(event) {
  console.log("request:", JSON.stringify(event, undefined, 2));

  // create AWS SDK clients
  const dynamo = new DynamoDB();
  const lambda = new Lambda();

  // update dynamo entry for "path" with hits++
  await dynamo.updateItem({
    TableName: process.env.HITS_TABLE_NAME,
    Key: { path: { S: event.path } },
    UpdateExpression: 'ADD hits :incr',
    ExpressionAttributeValues: { ':incr': { N: '1' } }
  }).promise();

  // call downstream function and capture response
  const resp = await lambda.invoke({
    FunctionName: process.env.DOWNSTREAM_FUNCTION_NAME,
    Payload: JSON.stringify(event)
  }).promise();

  console.log('downstream response:', JSON.stringify(resp, undefined, 2));

  // return response back to upstream caller
  return JSON.parse(resp.Payload);
};
```
20.xでこのままやってみると、
```
"errorType": "Runtime.ImportModuleError",
    "errorMessage": "Error: Cannot find module 'aws-sdk'\nRequire stack:\n- /var/task/hitcounter.js\n- /var/runtime/index.mjs",
    "stack": [
        "Runtime.ImportModuleError: Error: Cannot find module 'aws-sdk'",
```
このようなエラーになりました。
14.xではSDKのバージョンが2になっていますが、20.xの場合はSDKのバージョンが3になっているためその差異による修正が必要になります


## 修正内容
```lambda/hitcounter.mjs
import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";
import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda";

export const handler = async function (event) {
  console.log("request:", JSON.stringify(event, undefined, 2));

  // create AWS SDK clients
  const dynamo = new DynamoDBClient({});
  const lambda = new LambdaClient({});

  // update dynamo entry for "path" with hits++
  const dynamoInput = {
    TableName: process.env.HITS_TABLE_NAME,
    Key: { path: { S: event.path } },
    UpdateExpression: "ADD hits :incr",
    ExpressionAttributeValues: { ":incr": { N: "1" } },
  };
  const dynamoCommand = new UpdateItemCommand(dynamoInput);
  await dynamo.send(dynamoCommand);

  // call downstream function and capture response
  const lambdaInput = {
    FunctionName: process.env.DOWNSTREAM_FUNCTION_NAME,
    Payload: JSON.stringify(event),
  };
  const lambdaCommand = new InvokeCommand(lambdaInput);
  const lambdaResponse = await lambda.send(lambdaCommand);

  const result = Buffer.from(lambdaResponse.Payload).toString();
  console.log("downstream response:", result);

  // return response back to upstream caller
  return JSON.parse(result);
};
```

必要な変更点は以下でした
・SDKのバージョンアップによる変更(requireからimportへの変更、APIの変更や処理の仕方の変更)
・ESモジュールに対応させるための変更(.jsから.mjsへの変更、exportの仕方の変更)


## 修正内容までの流れ
まずは修正前に20.xでやった場合に出たSDK関連のエラーに対する、SDKのバージョンアップの対応をしました。
requireからimportへの変更、APIの変更や処理の仕方の変更です
```lambda/hitcounter.js
import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";
import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda";


exports.handler = async function(event) {
  console.log("request:", JSON.stringify(event, undefined, 2));

  // create AWS SDK clients
  const dynamo = new DynamoDBClient({});
  const lambda = new LambdaClient({});

  // update dynamo entry for "path" with hits++
  const dynamoInput = {
    TableName: process.env.HITS_TABLE_NAME,
    Key: { path: { S: event.path } },
    UpdateExpression: "ADD hits :incr",
    ExpressionAttributeValues: { ":incr": { N: "1" } },
  };
  const dynamoCommand = new UpdateItemCommand(dynamoInput);
  await dynamo.send(dynamoCommand);

  // call downstream function and capture response
  const lambdaInput = {
    FunctionName: process.env.DOWNSTREAM_FUNCTION_NAME,
    Payload: JSON.stringify(event),
  };
  const lambdaCommand = new InvokeCommand(lambdaInput);
  const lambdaResponse = await lambda.send(lambdaCommand);

  const result = Buffer.from(lambdaResponse.Payload).toString();
  console.log("downstream response:", result);

  // return response back to upstream caller
  return JSON.parse(result);
};
```
これでやってみると、
```
"errorType": "Runtime.UserCodeSyntaxError",
    "errorMessage": "SyntaxError: Cannot use import statement outside a module",
    "stack": [
        "Runtime.UserCodeSyntaxError: SyntaxError: Cannot use import statement outside a module",
```
今度は違うエラーになりました。

上記のSDKのバージョンアップの対応でimportを利用しているのでESモジュールとして扱わないといけないのですが、.jsはCommonJSモジュールとして扱われるため、.jsから.mjsとするとESモジュールとして扱われるようになります。
また、ESモジュールの場合でexportの仕方が変わるのでそれも変更すると、最終的に修正内容の形になり、無事エラーなく実行できるようになりました


## 参考
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-nodejs.html

https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v3/developer-guide/javascript_code_examples_categorized.html

https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/