DynamoDB Local用のViewerをSpring Bootベースで作ってみた

  • 2
    いいね
  • 0
    コメント

DynamoDB Localって便利ですよね。実際にAmazon DynamoDBにつなぎに行かなくてもCRUD操作をローカル環境で試せるので重宝しています。

java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb

このコマンドでDynamoDBがシミユレートできてしまうのだから、とても便利。

が、、、投入したデータの確認、となると一手間かかってしまいます。localhost:8000/shell(デフォルトの画面URL)で見れるこの画面、ありがたいのですが、今ひとつ物足りないですよね。

スクリーンショット 2017-01-04 19.54.49.png

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

スクリーンショット 2017-01-04 19.45.06.png

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

スクリーンショット 2017-01-04 19.56.45.png

とりあえず目下自分が困っていたのは投入したデータの確認が容易にできないことだったので、DynamoDB Localにつなぎに行ってデータの中身を可視化してくれるViewerを作成しました。

結果

こちらに登録してあります。ご自由にお使いください。起動方法などはREADME.mdをご覧ください。

https://github.com/kojiisd/dynamodb-local-view

かなり力技で実装している部分があります。ソートやフィルタは実装していません。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で開発しました。

機能と画面

listTablesscanしかしていません。画面も2画面しか作っていません。

1. テーブル一覧画面

DynamoDB Localを起動した状態でアプリケーションから接続に行きます。 localhost:8080 でページが見れます。この画面は現在DynamoDB Localに保存されているテーブルの一覧になります。

スクリーンショット 2017-01-22 16.35.32.png

各テーブルにはリンクを付与しており、クリックするとそのテーブルのデータ一覧が見れます。

2. テーブル詳細画面

こんな感じになっています。DynamoDBは列指向ですので、動的に列が増えたとしても、対応していないレコードは空で表示するような仕組みにしています。

スクリーンショット 2017-01-22 16.35.22.png

この例ではsensor_idカラムとtimestampカラムをそれぞれ「HASH」、「RANGE」にしてテーブル作成しています。

テーブル作成のサンプルデータ

今回のテーブル(sample_table3)を表示するためのテーブル作成定義とデータサンプルは以下になります。

とりあえずこのままDynamoDB Localの管理画面(localhost:8000/shellでアクセスできるアレ)に投入すれば、テーブルの作成はできます。

CreateTable用データ定義
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側でデータ保持して、画面側に渡し、表示するのがいいのか、については少し苦戦しました。

結果として以下のようにしました。

  1. [サーバ] 一旦値が空のキーのみのMapを作成する → カラムの過不足をなくす
  2. [サーバ] データが存在する部分だけ値を入れる → データが存在しない箇所は空文字になる
  3. [クライアント] ヘッダ作成部とデータ表示部でテーブルの作り方に一工夫

[サーバ] 一旦値が空のキーのみの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>