LoginSignup
3
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-01-22

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をご覧ください。

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