LambdaでDynamoDBに複数レコードの書き込みをする（Python、JavaScript）



## はじめに
DynamoDBに複数レコードを書き込み（更新）する必要があったので、実装してみた結果と注意的なものを書き留めたいと思い記事を投稿した
またPythonとNode.jsの両方での実装を載せようと思います

## 前提
### テーブル
今回書き込みをするテーブルは以下を想定
テーブル名：Users
カラムと型(スキーマ自体に型はありませんが、登録するデータの型として便宜上決めておきます)は以下の通り

- id(primary key)：Number
- name：String
- address：String
- friends： List[Map]

friendはid, name, addressのキーを持つMapの一覧を想定
今回は複雑めな型（Userテーブルのfriendのような型）であっても、書き込みができることも示しておきたいことの一つなので、そもそもテーブルの設計がベストであるかは一旦無視でいきます

### Lambdaの準備
今回はLambda上で動くコードを掲載するので、Lambdaは各自で準備ができていることを前提とします

### 記事の流れ
全体のコード例 → 軽い解説
の手順で書いていき、最後に共通する補足事項を載せる

## Pythonでの実装
```python
import boto3
from boto3.dynamodb.conditions import Key

def update_users(table, users_friends):
   with table.batch_writer() as batch:
      for n in range(3):
        batch.put_item(
          Item={
				"id": n + 1,
				"name": "user" + str(n + 1),
				"address": "address" + str(n + 1),
				"friends": users_friends[n]
          }
        )

def lambda_handler(event, context):
  try:
    dynamoDB = boto3.resource("dynamodb")
    table = dynamoDB.Table("Users")
	  user1_friends = [
		{ "id": 2, "name": "user2", "address": "address2" },
		{ "id": 3, "name": "user3", "address": "address3" }
	  ]
	  user2_friends = [
		{ "id": 1, "name": "user1", "address": "address1" },
		{ "id": 3, "name": "user3", "address": "address3" }
	  ]
	  user3_friends = [
		{ "id": 1, "name": "user1", "address": "address1" },
		{ "id": 2, "name": "user2", "address": "address2" }
	  ]
	  
	  users_friends = [user1_friends, user2_friends, user3_friends]
    
    update_users(table, users_friends)
    
    return event["message"] # 返すものは適当
  except Exception as e:
        print(e)
```

### 軽く解説
コードの書き方云々や書き込むデータ自体については本質ではないので、スルーしてもらって大丈夫です
大事なのは`update_users`関数の中身で、DynamoDBのモデルのインスタンスに生えている`batch_write`メソッドを使っているということ
すなわち、複数書き込みをしたいときは以下のブロック内で`PUT`をする

```python
with table.batch_write() as batch:
	# ...略
```

また、`PUT`する際には例でいうと`batch.put_item`を呼び出してやる
こいつの`Item`という名前の引数に書き込みの内容を記述していく
そしてもう一点注目したいのが、List[Map]という型であっても問題なく書き込むことができるという点
基本的にどんな型でも指定したキーの値としてちゃんとDBに書き込めるので、要するに`Item`には更新したい辞書をそのまま渡してあげれば問題ない
最後にちょっとした補足として、`batch_write`のブロック内で`for`文を回しているが、当然`batch.put_item`をベタで複数回書いていってもよいので、もし`for`文では書けないときはベタでやればいい
また、一度にできる書き込める上限は25件までと言われているが、どうやら25件以上の場合は再送してくれたりよしなにやってくれているらしいので、件数を気にせずコードを書いていけるっぽい

## Node.jsでの実装
```js
const AWS = require("aws-sdk");
const dynamoDB = new AWS.DynamoDB.DocumentClient({
  region: "ap-northeast-1"
});

const tableName = "Users";

exports.handler = (event, context) => {
	  const user1Friends = [
		{ id: 2, name: "user2", address: "address2" },
		{ id: 3, name: "user3", address: "address3" }
	  ];
	  const user2Friends = [
		{ id: 1, name: "user1", address: "address1" },
		{ id: 3, name: "user3", address: "address3" }
	  ];
	  const user3Friends = [
		{ id: 1, name: "user1", address: "address1" },
		{ id: 2, name: "user2", address: "address2" }
	  ];
	  const usersFriends = [user1Friends, user2Friends, user3Friends]

    const params = {
		RequestItems: {
		  [tableName]: usersFriends.map((e, i) => ({
			PutRequest: {
			  Item: {
			    id: i + 1,
			    name: `user${i + 1}`,
			    address: `address${i + 1}`,
			    friends: e
			  }
			}
		  }))
		}
	  };
    
// callbackは適当
    dynamoDB.batchWrite(params, (e, data) => {
		if (e) {
		  context.fail(e);
		} else {
		  context.succeed(data);
		}
    });
};
```

### 軽い解説
Pythonでの解説で述べたようにコードやデータの値そのものに関してはスルー
ここで大事なのは`AWS.DynamoDB.DocumentClient`にある`batchWrite`というプロパティ（関数）
こいつの引数は結構癖があるので[Class: AWS.DynamoDB.DocumentClient      — AWS SDK for JavaScript](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#batchWrite-property)を参照してください
Node.jsでの複数書き込みについて調べたときによく出てくるのは`batchWriteItem`だったのですが、現在（2020年9月時点）のLambdaで用意されている最新のNode.jsの実行環境だとどうやらないっぽいので、動かず意外と手こずる
`batchWriteItem`との大きな違いは`Item`のオブジェクトがDynamoDB特有の型付きのオブジェクト(`name: { "S": "user" }`みたいなやつ)である必要がなくなっている点
要するに書き込みたい値を気にせずそのままJSONにして`Item`の値として渡せば問題なく動く
そのため、当然Map[List]という複雑な型であってもそのまま渡せば書き込み可能です

## 全体の補足
バッチ処理で更新(update)をしたいときは`PUT`を使うしかない
理由は単純で`UPDATE`がないからで、仕方ないので地道に`PUT`で記述していく必要がある
またバッチ処理内では`DELETE`をすることができるので、今回はフォーカスしませんでしたが、一応あるということだけ言っておきます・・・

## おわりに
いかがでしたでしょうか
複数操作についてはコードと1, 2行程度の解説がある記事はちょこちょこ見かけますが、書き込みに特化して解説されているのはあまり見かけず（あっても複雑な型でもできるのか不明であったり、そもそも実行時エラーになってしまう）、多少やってて手こずったので、備忘の意味も兼ねて書き残してみました
若干ニッチな分野かもしれないですが、少しでも役に立てて貰えれば幸いです

## 参考
[Amazon DynamoDB — Boto3 Docs 1.14.56 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/dynamodb.html#batch-writing)
[Class: AWS.DynamoDB.DocumentClient      — AWS SDK for JavaScript](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#batchWrite-property)