LoginSignup
16
9

More than 3 years have passed since last update.

DynamoDBを使いやすくしたJavascript(Typescript)のライブラリを作成したので紹介します

Last updated at Posted at 2019-07-11

NodeDynamoDBORM というDynamoDBRailsActiveRecordのように使うことができるNode.jsライブラリを作成しましたので紹介します。
よかったら、使ってください。またスターもください。
プルリクやご要望もお待ちしています。
DynamoDBについては後ろの方に紹介しているのでそちらもご覧ください。

イントロダクション

インストール

npm install node-dynamodb-orm --save

または、yarnを使う場合は

yarn add node-dynamodb-orm

でインストールします。

特徴

RailsActiveRecordの使用感に合わせた形で作成しました。ActiveRecordと大体同じ感覚で使え、ActiveRecordの機能をできるだけ再現できるように作りました。

なぜ作った?

公式のaws-sdkがすごく使いにくい!!

aws-sdk を使ってDynamoDBを操作する開発に限界を感じ、使いやすい ORM も存在しなかったため作成しました。(共通の悩みを持っている人も多くいるようです(参考を参照))
機能の追加希望などの意見がありましたら教えてください。プルリクもお待ちしています。

参考

DynamoDB を使う

ローカル環境での開発環境の構築

プロジェクト(NodeDynamoDBORM)の中ではDynamoDBの環境をDockerを用いてローカル環境を構築し、Jestでテストを記述しています。
上記プロジェクトをダウンロード後、

docker-compose up

と実行することでDynamoDBのローカルサーバーが起動します。
サーバーが起動した後 http://localhost:8000/shell/ にアクセスすることでDynamoDBJavascript Consoleが開くので、こちらでいろいろと試すことができます。

参考

AWS コンソールから DynamoDB を使う

  1. AWS コンソールから DynamoDB を選択することで各種設定を行うことができます。
  2. 初めてならば、とりあえずテーブルを作成していくことで使用できます。 DynamoDBCreateTable.png
  3. 外部から DynamoDB にアクセスするためにはIAMにて DynamoDB へのアクセス権限を付与し、付与したアカウントの accessKeysecretAccessKey を取得します。

DynamoDBFullAccess.png

上記のように DynamoDB にアクセスすることができるアカウントのaccessKeyIdsecretAccessKeyを用いることで、AWS コンソール上に作った DynamoDB の操作ができるようになります。
また AWS コンソールからだけでなくaws-cliを用いてcliからの操作も可能です。その場合は上記IAMにて取得した、accessKeyIdsecretAccessKey を用いることで DynamoDB にアクセスすることができます。

Node.js での使い方

import/require

使用するためには以下のように読み込みます。

const { DynamoDBORM } = require("node-dynamodb-orm");

または TypeScript など import が使用できる場面では

import { DynamoDBORM } from 'node-dynamodb-orm';

これで宣言できます。

接続情報の設定

読み込んだら、DynamoDB に接続するための接続情報を設定します。
process.env内に以下に指定した値を設定されていれば自動的に読み込んで接続することもできます。そのため、dotenvを用いて.envファイルに記載した場合その設定が接続に反映されます。

.env への記述の仕方

接続情報の例を以下に示します。

region=DynamoDB's AWS Region
endpoint=endpoint (ローカルなら http://localhost:8000 それ以外はなくてもOK)
accessKeyId=xxxxx
secretAccessKey=xxxxxxxxxx

regionは必須です。
ローカルの DynamoDB に接続したい場合はendpointを指定して下さい。

accessKeyId=xxxxx
secretAccessKey=xxxxxxxxxx

は、上記 AWS コンソールでの設定にて、DynamoDB へのアクセスが可能な権限を持ったaccessKeyIdsecretAccessKeyをそれぞれ使用することで使用することができます。

接続情報を直接指定する場合

ソースコードの中で直接指定することもできます。その場合は以下のように指定します。

DynamoDBORM.updateConfig({ region: 'ap-northeast-1', endpoint: 'http://localhost:8000' });

この場合、ローカルの DynamoDB へのアクセスとなります。
accessKeyIdsecretAccessKeyを用いた場合は以下のようになります。

DynamoDBORM.updateConfig({ region: 'ap-northeast-1', accessKeyId: 'accessKeyId', secretAccessKey: 'secretAccessKey' });

Node.js から呼び出す

今回、以下のようなusersテーブルがすでに作成されているとします。

項目
テーブル名 users
プライマリキー(ハッシュキー) user_id
プライマリキー(ハッシュキー)のデータ種別 文字列

また users の持ち物として items テーブルが以下のように定義され、すでに作成されているとします。 また items テーブルは users テーブルと依存関係にあります。

項目
テーブル名 items
プライマリキー(ハッシュキー) user_id
プライマリキー(ハッシュキー)のデータ種別 文字列
プライマリキー(レンジキー) mst_item_id
プライマリキー(レンジキー)のデータ種別 文字列

これらのテーブルを用いた操作の例を以下に紹介していきます。

  • ハッシュキー = パーティションキー
  • レンジキー = ソートキー

ともいいます。

New

まずはテーブル名を引数にインスタンスを作成します。

const usersTable = new DynamoDBORM('users');

CRUD

Create

テーブルにデータを追加

const user = await usersTable.create({ user_id: 'user_id', name: 'sample' });

テーブル内のデータは以下のようになります。

user_id name
user_id sample

Update

テーブル内のデータを更新

const user = await usersTable.update({ user_id: 'user_id' }, { email: 'xxxx', name: 'sample2' });

テーブル内のデータは以下のようになります。

user_id name email
user_id sample2 xxxx

※ DynamoDB ではプライマリキーのデータの更新はできないので注意してください

Read

テーブル内からデータを取得

const user = await usersTable.findBy({ user_id: 'user_id' });
//user => {user_id: "user_id", name: "sample2", email: "xxxx"}

※ この時、プライマリキーを指定しなければなりません。指定しなかった場合はエラーになります

Delete

テーブル内からデータを削除

const isDeleteSuccess = await usersTable.delete({ user_id: 'user_id' });

テーブル内のデータは以下のようになります。

user_id name email

一括操作系

ここでは items テーブルへの操作例を紹介します。

const itemsTable = new DynamoDBORM('items');

データの一括追加

テーブルに複数件のデータを追加するには以下のようになります。

const items = await itemsTable.import([
  { user_id: 'user_id1', mst_item_id: 1, amount: 1 },
  { user_id: 'user_id1', mst_item_id: 2, amount: 2 },
  { user_id: 'user_id2', mst_item_id: 2, amount: 2 },
]);

items テーブル内のデータは以下のようになります。

user_id mst_item_id amount
user_id1 1 1
user_id1 2 2
user_id2 2 2

複数のデータを取得

テーブル内から複数のデータを取得

const items = await itemsTable.findByAll({ user_id: 'user_id1' });
//=> [{ user_id: 'user_id1', mst_item_id: 1, amount: 1 },{ user_id: 'user_id1', mst_item_id: 2, amount: 2 }]

または where を使って同様のこともできます。

const items = await itemsTable.where({ user_id: 'user_id1' }).load();
//=> [{ user_id: 'user_id1', mst_item_id: 1, amount: 1 },{ user_id: 'user_id1', mst_item_id: 2, amount: 2 }]

load() を実行しないとデータを取得できないので、注意です。

テーブル内のデータ全件を取得したい

テーブル内に含まれているデータを全て取得することもできます。

const items = await itemsTable.all();
//=> [{ user_id: 'user_id1', mst_item_id: 1, amount: 1 },{ user_id: 'user_id1', mst_item_id: 2, amount: 2 },{ user_id: 'user_id', mst_item_id: 2, amount: 2 }]

ページングしたい

DynamoDB では Query(findByAll, whereで使用) で取得できるデータは 1MB までです。

途中までしか取得できなかったデータを取得するためには取得できなかった先の情報にアクセスできる必要があります。そのような場合はoffset を指定することでデータを取得することができます。

const items = await itemsTable.offset({ user_id: 'user_id1', mst_item_id: 2 }).load();
//=> [{ user_id: 'user_id', mst_item_id: 2, amount: 2 }]

DynamoDB の仕様上 offset として指定できるのは プライマリキー の組み合わせを指定します。

参考

一括データ削除

テーブル内から複数のデータを削除

const items = await itemsTable.deleteAll([{ user_id: 'user_id1', mst_item_id: 1 }, { user_id: 'user_id1', mst_item_id: 2 }]);

items テーブル内のデータは以下のようになります。

user_id mst_item_id amount
user_id2 2 2

あれ?なんか一部 ActiveRecrod と違くない?

扱っている相手が DynamoDB なので、そこは少し仕様が異なります

トランザクション

更新系処理を一括に行います。

const itemsTable = new DynamoDBORM('items');
const items = await itemsTable.import([
  { user_id: 'user_id1', mst_item_id: 1, amount: 1 },
  { user_id: 'user_id2', mst_item_id: 2, amount: 2 },
]);
await itemsTable.transaction(async () => {
  await itemsTable.create({ user_id: 'user_id3', mst_item_id: 3, amount: 3 });
  await itemsTable.update({ user_id: 'user_id2', mst_item_id: 2}, { amount: 3 });
  await itemsTable.delete({ user_id: 'user_id1', mst_item_id: 1 });
});

上のような例を実行すると、items テーブルの中は

user_id mst_item_id amount
user_id2 2 3
user_id3 3 3

という結果になります。

また、インスタンスを作らず実行することもでき、その場合、上記の内容は

const itemsTable = new DynamoDBORM('items');
const items = await itemsTable.import([
  { user_id: 'user_id1', mst_item_id: 1, amount: 1 },
  { user_id: 'user_id2', mst_item_id: 2, amount: 2 },
]);
await DynamoDBORM.transaction(async () => {
  await itemsTable.create({ user_id: 'user_id3', mst_item_id: 3, amount: 3 });
  await itemsTable.update({ user_id: 'user_id2', mst_item_id: 2}, { amount: 3 });
  await itemsTable.delete({ user_id: 'user_id1', mst_item_id: 1 });
});

とすることもできます。
transactionメソッドの中では複数のテーブルに更新を行う場合であっても、一括に実行してくれます。

※注意点として、MySQL等のトランザクションとは異なり、同じアイテムを同時に更新することはできません

Error: ValidationException Transaction request cannot include multiple operations on one item

参考

もう少し使っている感じをイメージしたい

Jest でテストを記述してあります。こちらを参照した方が使い方のイメージがつかみやすいかもしれません。そのほかの機能についても記述しています。

DynamoDB の基礎知識

そもそもとして、DynamoDBを扱う上での基礎知識や特徴、仕様について以下に紹介します。

DynamoDB の魅力

  • 安価(無料)で利用できる NoSQL データベース!!
  • オートスケールもしてくれる → 高負荷にも強い
  • 大規模データの保存/ハッシュキーによるデータの取得が高速
  • カラムを追加する必要がない

DynamoDB デメリット

  • 検索するときはプライマリキー(ハッシュキー)を指定しなければならない
  • 複雑な操作はできない(ソートとか JOIN とか GROUPING とか)
  • バリデーションが弱い(例: プライマリキーが重複したものを追加しようとすると、エラーが起こらず、そのデータが「更新」される)

DynamoDB の特性・仕様の解説

DynamoDBは根本的には KVSNoSQL データベースです。そのため、RDB と同じ考え方操作することができません。また、RDB の考え方でデータ構造を作ると苦労するので気をつけてください。

1. 複合プライマリキーとして設定できるキーは 2 種類まで

複合プライマリキー(ハッシュキーとレンジキー)として設定できるキーは 2 種類までであるので、データベースの正規化を行う場合は制約がかかります。
上記のitemsテーブルがその例で、上記以上のリレーションを構築することはDynamoDBではできません。そのため、データ構造はよく考えて構築する必要があります。

2. AUTO INCREMENT なんてものは存在しない

プライマリキーとして指定した Key は重複してはならない値になります。MySQLなどには自動で重複しない値を設定してくれるAUTO INCREMENT属性がありますが、DynamoDB にはそのようなものはありません。そのため、[uuid(https://github.com/kelektiv/node-uuid) などを使い、以下のように自力で重複しない値を生成する必要があります。

const uuid = require('uuid/v4');
uuid();

3. 検索する時はプライマリキー(ハッシュキー)の指定が必須

例えばMySQLでは上記のitems テーブルのようなデータの場合

SELECT * FROM items WHERE mst_item_id = 2;

のようにレンジキーのみを指定したり

SELECT * FROM items WHERE amount = 2;

他のカラムのみを検索条件に指定して SQL で検索することができます。

しかし、DynamoDB ではハッシュキーである user_id が等価であることを指定した上で他の条件を入力しなければエラーになってしまいます。

以上のようにDynamoDBは RDB と性質が異なるデータベースであるため。用途に合わせるか使い方を工夫して使用する必要があります

参考

今後実装予定の機能について

四則演算、前方一致

実装予定の使用例

const itemsTable = new DynamoDBORM('items');
const items = await itemsTable.where('amount < 2').load();

find_each, find_in_batches

実装予定の使用例

const itemsTable = new DynamoDBORM('items');
itemsTable.findEach((item) => {
  // 何か処理を書く
});

いずれも現在、ブランチを作成して機能を作成しています。作成までしばしお待ちを。

最後に

ぜひ使ってみてください。
また、バグとかありましたらご報告いただけると幸いです。(プルリクだとなお嬉しい)

16
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
9