DynamoDBをLocalで検証してSDKの違いを確認する
AWS SDKの制御が古いバージョンと新しいバージョンで違うとの話があったので、DynamoDBLocal使いながら検証してみた。
- 環境
- とりあえずDynamoを用意する
- DynamoDBLocalにテーブルやデータを準備する
- 旧バージョンSDKのv1APIで検証 → できなかった
- 旧バージョンSDKのv2APIで検証
- 新バージョンSDKで検証
- 新バージョンSDKの動きを旧バージョンに合わせてみる
- まとめ
環境
とりあえずDynamoを用意する
DynamoDBLocalって?
AWSアカウント作ったから本物使ってもいいんだけど、使い方次第で金かかるから不安やん。
という1USDもAWSには金を払いたくない人向けに、現在はローカルで擬似Dynamoを立ち上げることができるのである。便利な世の中になったもんだ。
Dynamoと同一インタフェースで操作できる上にSQLiteでデータ永続化を実現、さらにはJavaScriptSDKを利用してブラウザから操作する機能まで一緒についていると気軽に起動できる割には至れりつくせり。
ローカルだとほぼ発生しないと書いてあるものの結果整合性も擬似ってるらしい。いったいどうやってんだ。
なお、最新のAWS Toolkit for Eclipseにも付属しているとのこと。
注1:あくまで擬似環境なので、同一インタフェースを謳っていても本物を利用した試験を怠ってはいけない。
注2:ちゃんと確認してないものの、リリースされたばかりのような最新機能は未実装もあると思われる。
DynamoDBLocalをダウンロードする
AWS公式から手に入る。いろいろググれば古いバージョンも見つかる。
コンピュータ上の DynamoDB (ダウンロード可能バージョン) - Amazon DynamoDB
自環境では「アジアパシフィック (東京) リージョン」のzipを入手。
ダウンロードページに書いてあるのだが、中身は実行可能jarなのでJava実行環境が必要となる。
コンピュータで DynamoDB を実行するには、Java Runtime Environment (JRE) 6.x 以降のバージョンが必要です。アプリケーションは、以前のバージョンの JRE では動作しません。
DynamoDBLocalを起動する
zipファイルを解凍するとjarが入っている。
jarと同じパスで以下のようにコマンドライン打てば一発で起動できる。めっちゃ簡単。
java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb
いろいろカスタムする場合は下記に説明書がついている。
DynamoDB 使用に関する注意事項 - Amazon DynamoDB
コマンドラインヘルプを参照するなら下記から。
java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -help
今回の検証範囲なら前述の起動コマンドのままでOK。
デフォルトではポート8000で待ち受けるDynamoDBLocalが起動する。
また、起動後下記にWebブラウザで接続するとブラウザ経由での操作もできるようになっている。
http://localhost:8000/shell/
マネジメントコンソールとはだいぶ違いがあり、JavaScriptSDKでいじれる便利環境が手に入る。
DynamoDBLocalにテーブルやデータを準備する
取り急ぎはブラウザ経由のJavaScript操作が手軽だったのでそこからやってみた。
テーブル作成
検証用のテーブルを作成する。
メニューからCreateTableのテンプレートは作ってくれるのでそれベースでちゃちゃっと。
HashKeyとしてそのままhash_key
(String)を指定したtest_table
を作成。
var params = { TableName: 'test_table', KeySchema: [ // The type of of schema. Must start with a HASH type, with an optional second RANGE. { // Required HASH type attribute AttributeName: 'hash_key', KeyType: 'HASH', }, ], AttributeDefinitions: [ // The names and types of all primary and index key attributes only { AttributeName: 'hash_key', AttributeType: 'S', // (S | N | B) for string, number, binary }, ], ProvisionedThroughput: { // required provisioned throughput for the table ReadCapacityUnits: 1, WriteCapacityUnits: 1, }, }; dynamodb.createTable(params, function(err, data) { if (err) ppJson(err); // an error occurred else ppJson(data); // successful response });
テーブル一覧
これはListTablesのテンプレートそのままでOK…と思いきやデフォルトのLimit: 0
が許容範囲外である。
optionalと言っているからとりあえずLimitは削除して確認。
var params = { ExclusiveStartTableName: 'table_name', // optional (for pagination, returned as LastEvaluatedTableName) }; dynamodb.listTables(params, function(err, data) { if (err) ppJson(err); // an error occurred else ppJson(data); // successful response });
テーブルに適当にデータ突っ込む
PutItemのテンプレートがっつり削った。なんか「登録されたよ」的な出力はないけど成功はしているみたい。
var params = { TableName: 'test_table', Item: { // a map of attribute name to AttributeValue 'hash_key' : 'testKey', 'unique_value' : 'testValue1', }, }; docClient.put(params, function(err, data) { if (err) ppJson(err); // an error occurred else ppJson(data); // successful response });
テーブルの中身確認
Scanのテンプレートをがっつり削って実行。
var params = { TableName: 'test_table', }; dynamodb.scan(params, function(err, data) { if (err) ppJson(err); // an error occurred else ppJson(data); // successful response });
旧バージョンSDKのv1APIで検証 → できなかった
強制的にSessionTokenAPI叩いてしまうの突破するためにめんどくさいコード組んでみたのだけど、結果的にそこ突破してもDynamoDBLocalはv1APIで叩けないことが判明。
DynamoDBLocalから下記のようにエラーメッセージが返却される。
DynamoDB Local does not support v1 API
ちなみにv2APIすら実装されていない1.3.xなどのSDKを使うと上記メッセージすら出ずに認証エラーを示すレスポンスが返却されてハマる。
その際のメッセージは下記の通り。どちらにしろLocal検証することはできない。
AWS Error Code: MissingAuthenticationToken, AWS Error Message: Request must contain either a valid (registered) AWS access key ID or X.509 certificate
DEBUGログで確認してみたけど、どうもv2APIが実装された段階のSDKで、v1APIで送るパラメータも少し変わっていたようである。
旧バージョンSDKのv2APIで検証
そして旧バージョンSDK用のJavaコード
こんな感じで@DynamoDBVersionAttribute
をStringのAttributeで使った場合にどうなるかが検証対象。
※ 本来AWS側が意図している@DynamoDBVersionAttribute
はNumberかByteの属性に利用が限定されている。実際に組む際にString属性への付与はやってはいけない。これは間違ってやってしまったときの動作を検証する遊びです。
package com.sample.dynamotest; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig.ConsistentReads; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBVersionAttribute; /** * Java SDK 1.4.x検証用のコードです */ public class OldDynamoTest { @DynamoDBTable(tableName = "test_table") public static class TestTable { private String hashKey; private String uniqueValue; @DynamoDBHashKey(attributeName = "hash_key") public String getHashKey() { return hashKey; } public void setHashKey(String hashKey) { this.hashKey = hashKey; } @DynamoDBVersionAttribute @DynamoDBAttribute(attributeName = "unique_value") public String getUniqueValue() { return uniqueValue; } public void setUniqueValue(String uniqueValue) { this.uniqueValue = uniqueValue; } } public static void main(String[] args) { // 適当なAccessKey/SecretKeyを指定した上で、DynamoDBLocalの起動している先にエンドポイントを向ける AmazonDynamoDBClient client = new AmazonDynamoDBClient(new BasicAWSCredentials("fakeAKey", "fakeSKey")); client.setEndpoint("http://localhost:8000"); DynamoDBMapper mapper = new DynamoDBMapper(client); // 検証用データを準備 TestTable data = new TestTable(); data.setHashKey("testKey"); data.setUniqueValue("testValue1"); // 投げてみて検証! // loadなら… // TestTable loaded = mapper.load(data, new DynamoDBMapperConfig(ConsistentReads.CONSISTENT)); // saveなら… // mapper.save(data, new DynamoDBMapperConfig(ConsistentReads.CONSISTENT)); // deleteなら… mapper.delete(data, new DynamoDBMapperConfig(ConsistentReads.CONSISTENT)); } }
旧バージョンSDKの検証結果
save/delete/loadの挙動を確認してみた。
使用API(+パターン) | 挙動 |
---|---|
save | リクエスト前にエラー(DynamoDBMappingException) |
delete(HashKey存在せず) | リクエストして整合性チェックエラー(ConditionalCheckFailedException) |
delete(HashKeyに紐付くuniqueValueが一致) | 成功 |
delete(HashKeyに紐付くuniqueValueが不一致) | リクエストして整合性チェックエラー(ConditionalCheckFailedException) |
load | 成功 |
新バージョンSDKで検証
新バージョンSDKの動きを旧バージョンに合わせてみる
Stringに無理やり付与した@DynamoDBVersionAttribute
が特定の効き方をしていたのはdelete限定なので、そこの動きが合うように組み直してみる。
アノテーション貼っているとチェックエラーでどうしようもないので、外しつつ自前でExpectするのが妥当なのだろう。
組み直し版のJavaコード
アノテーション外して、自前Expect加える。本当はdeprecated
消したかったけど、AccessKey/SecretKeyベタ書き方式から切り替えないとダメっぽかったので一旦諦め。
package com.sample.dynamotest; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBDeleteExpression; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig.ConsistentReads; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.amazonaws.services.dynamodbv2.model.ExpectedAttributeValue; /** * Java SDK 1.11.x検証用のコードです */ public class NewDynamoTest { @DynamoDBTable(tableName = "test_table") public static class TestTable { private String hashKey; private String uniqueValue; @DynamoDBHashKey(attributeName = "hash_key") public String getHashKey() { return hashKey; } public void setHashKey(String hashKey) { this.hashKey = hashKey; } // @DynamoDBVersionAttributeを削除 @DynamoDBAttribute(attributeName = "unique_value") public String getUniqueValue() { return uniqueValue; } public void setUniqueValue(String uniqueValue) { this.uniqueValue = uniqueValue; } } public static void main(String[] args) { // 適当なAccessKey/SecretKeyを指定した上で、DynamoDBLocalの起動している先にエンドポイントを向ける AmazonDynamoDBClient client = new AmazonDynamoDBClient(new BasicAWSCredentials("fakeAKey", "fakeSKey")); client.setEndpoint("http://localhost:8000"); DynamoDBMapper mapper = new DynamoDBMapper(client); // 検証用データを準備 TestTable data = new TestTable(); data.setHashKey("testKey"); data.setUniqueValue("testValue2"); // 投げてみて検証! // loadなら… // TestTable loaded = mapper.load(data, new DynamoDBMapperConfig(ConsistentReads.CONSISTENT)); // saveなら… // mapper.save(data, new DynamoDBMapperConfig(ConsistentReads.CONSISTENT)); // DynamoDBDeleteExpressionで削除用の条件を追加する DynamoDBDeleteExpression delExpression = new DynamoDBDeleteExpression(); delExpression .withExpectedEntry("unique_value", new ExpectedAttributeValue(new AttributeValue(data.uniqueValue))); mapper.delete(data, delExpression, new DynamoDBMapperConfig(ConsistentReads.CONSISTENT)); } }
ExpectedAttributeValue
のカラム名がベタ書きなのが微妙に気に入らない…
組み直し後の検証結果
DEBUGログのリクエスト内容見ると違いは見られるものの、外部仕様レベルで旧SDKと大体等価にはできた。
付随してsaveが正常動作するようにもなるが、さすがに動かなかったものが動くようになる点は許容とする。
使用API(+パターン) | 挙動 |
---|---|
save | 成功(元々動いてなかった部分なので差分は気にしない) |
delete(HashKey存在せず) | リクエストして整合性チェックエラー(ConditionalCheckFailedException) |
delete(HashKeyに紐付くuniqueValueが一致) | 成功 |
delete(HashKeyに紐付くuniqueValueが不一致) | リクエストして整合性チェックエラー(ConditionalCheckFailedException) |
load | 成功 |