DynamoDB Localって便利ですよね。実際にAmazon DynamoDBにつなぎに行かなくてもCRUD操作をローカル環境で試せるので重宝しています。
java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb
このコマンドでDynamoDBがシミユレートできてしまうのだから、とても便利。
が、、、投入したデータの確認、となると一手間かかってしまいます。localhost:8000/shell
(デフォルトの画面URL)で見れるこの画面、ありがたいのですが、今ひとつ物足りないですよね。

やっぱり本家のこういう画面が欲しいなあ、、、と。こういうテーブル一覧とか、、、

各テーブルの詳細画面とか。

とりあえず目下自分が困っていたのは投入したデータの確認が容易にできないことだったので、DynamoDB Localにつなぎに行ってデータの中身を可視化してくれるViewerを作成しました。
結果
こちらに登録してあります。ご自由にお使いください。起動方法などはREADME.mdをご覧ください。
かなり力技で実装している部分があります。ソートやフィルタは実装していません。Pull Requestしてくれる方、大歓迎です。またデータ増えた時にパフォーマンス大丈夫かとかその辺は、知らんがな(´・ω・`)。今回の要件ではそんなにローカルでデータ保持しない想定だからダイジョーブ。
Mavenでのビルドから、Javaコマンドで実行できます。
$ mvn clean package
$ java -jar target/dynamodb-view-0.0.1-SNAPSHOT.jar
前提
-
2016-05-17_1.0
バージョンのDynamoDB Local jarファイルを利用しました。 - Spring Boot + AngularJSで開発しました。
機能と画面
listTables
とscan
しかしていません。画面も2画面しか作っていません。
1. テーブル一覧画面
DynamoDB Localを起動した状態でアプリケーションから接続に行きます。 localhost:8080
でページが見れます。この画面は現在DynamoDB Localに保存されているテーブルの一覧になります。
各テーブルにはリンクを付与しており、クリックするとそのテーブルのデータ一覧が見れます。
2. テーブル詳細画面
こんな感じになっています。DynamoDBは列指向ですので、動的に列が増えたとしても、対応していないレコードは空で表示するような仕組みにしています。
この例ではsensor_idカラムとtimestampカラムをそれぞれ「HASH」、「RANGE」にしてテーブル作成しています。
テーブル作成のサンプルデータ
今回のテーブル(sample_table3)を表示するためのテーブル作成定義とデータサンプルは以下になります。
とりあえずこのままDynamoDB Localの管理画面(localhost:8000/shellでアクセスできるアレ)に投入すれば、テーブルの作成はできます。
var params = {
TableName: "sample_table3",
KeySchema: [
{
AttributeName: "sensor_id",
KeyType: "HASH"
},
{
AttributeName: "timestamp",
KeyType: "RANGE"
}
],
AttributeDefinitions: [
{
AttributeName: "sensor_id",
AttributeType: "S"
},
{
AttributeName: "timestamp",
AttributeType: "S"
}
],
ProvisionedThroughput: {
ReadCapacityUnits: 1,
WriteCapacityUnits: 1
}
};
dynamodb.createTable(params, function(err, data) {
if (err) ppJson(err); // an error occurred
else ppJson(data); // successful response
});
データ作成はこんな感じでできます。
var params = {
TableName: "sample_table3",
Item: {
sensor_id: "acceleration-sensor01",
accelerationX: 1.254,
accelerationY: 0.001,
accelerationZ: 0.178,
timestamp: "2017-01-22T10:01:24"
},
ReturnValues: "NONE",
ReturnConsumedCapacity: "NONE",
ReturnItemCollectionMetrics: "NONE"
};
docClient.put(params, function(err, data) {
if (err) ppJson(err); // an error occurred
else ppJson(data); // successful response
});
最後に
今回のツールのおかげで、個人的にDynamoDB Localに保持したデータの確認がかなり容易になりました。このツールを使ってバシバシデータの確認をしてもらえたら、と思います。
おまけ(少し困ったところ)
今回列指向のデータベースに対するアクセスでしたので、各レコードが持っている異なるカラムを、どのようにJava側でデータ保持して、画面側に渡し、表示するのがいいのか、については少し苦戦しました。
結果として以下のようにしました。
- [サーバ] 一旦値が空のキーのみのMapを作成する → カラムの過不足をなくす
- [サーバ] データが存在する部分だけ値を入れる → データが存在しない箇所は空文字になる
- [クライアント] ヘッダ作成部とデータ表示部でテーブルの作り方に一工夫
[サーバ] 一旦値が空のキーのみのMapを作成する
この辺りの実装ですね。少し力技です。画面側の表示を簡単にするためにサーバ側に処理を多めに入れています。
private Map<String, String> createEmptyColumnMap(ScanResult scanResult) {
Map<String, String> columnMap = new LinkedHashMap<String, String>();
for (Map<String, AttributeValue> valueMap : scanResult.getItems()) {
for (Map.Entry<String, AttributeValue> valueMapEntry : valueMap.entrySet()) {
columnMap.put(valueMapEntry.getKey(), StringUtils.EMPTY);
}
}
return columnMap;
}
[サーバ] データが存在する部分だけ値を入れる
ここも力技。今回いくつかの型には(全部ではない)対応しましたが、これってDynamoDBのAPIを利用してもっとうまくできないですかね?
private String extractColumnValue(AttributeValue value) {
String result = StringUtils.EMPTY;
if (value == null) {
return result;
}
if (value.getBOOL() != null) {
result = value.getBOOL().toString();
} else if (value.getB() != null) {
result = value.getB().toString();
} else if (value.getN() != null) {
result = value.getN();
} else if (value.getS() != null) {
result = value.getS();
} else if (value.getM() != null) {
ObjectMapper mapper = new ObjectMapper();
try {
result = mapper.writeValueAsString(value.getM());
} catch (JsonProcessingException ex) {
// TODO Exception handling
ex.printStackTrace();
result = StringUtils.EMPTY;
}
} else if (value.getSS() != null) {
result = String.join(VALUE_SEPARATOR, value.getSS().toArray(new String[0]));
} else if (value.getNS() != null) {
result = String.join(VALUE_SEPARATOR, value.getNS().toArray(new String[0]));
} else if (value.getBS() != null) {
result = String.join(VALUE_SEPARATOR, value.getBS().toArray(new String[0]));
}
return result;
}
[クライアント] ヘッダ作成部とデータ表示部でテーブルの作り方に一工夫
ng-repeatを使ってAngularJS側でパッとテーブル表示したかったのですが、カラム名が動的に変わるのでこんな感じの実装になりました。limitTo:
を使ってほぼ無理矢理です。これもよりスマートなやり方があればぜひ知りたいです。
<table class="table table-condensed table-bordered table-striped">
<thead>
<tr ng-repeat="data in datas | limitTo:1">
<th>No</th>
<th ng-repeat="(key, val) in data">{{ key }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="data in datas">
<td>{{ $index + 1 }}</td>
<td ng-repeat="(key, val) in data">{{ val }}</td>
</tr>
</tbody>
</table>