In the last post in the series we showed how to setup a DynamoDb container through Docker, and connect to that from a running java client. But that just created a database instance and not the data structures that we’re going to need.
There’s two ways to do achieve this for our purposes:
Because there’s an outstanding feature request for the AWS CLI that aims to provide a local uri param for CloudFormation, I don’t think I can fully automate the CloudFormation setup yet within JUnit, so here’s the basis of setting up a DynamoDB table using Java’s SDK:
private void createCustomerPreferenceTable() {
CreateTableRequest createTableRequest = new CreateTableRequest();
createTableRequest.setTableName("CustomerPreferences");
createTableRequest.setBillingMode("PAY_PER_REQUEST");
createTableRequest.setAttributeDefinitions(Arrays.asList(
new AttributeDefinition("customerId", ScalarAttributeType.S)
));
createTableRequest.setKeySchema(Arrays.asList(
new KeySchemaElement("customerId", KeyType.HASH)
));
TestUtils.getClientDynamoDB().createTable(createTableRequest);
}The important aspects of this API call here are:
But I don’t want to write this every time, that’s going to get old quickly… and I’m a lazy developer.
If you’re using DynamoDb’s Mapper Framework, you’ve probably already annotated a lot of your Entities with @DynamoDbTable annotations, to tell the mapper its target right?
And that also has other useful things like @DynamoDBHashKey and @DynamoDBRangeKey, wouldn’t it be nice if we could leverage what we’ve already got in our code without adding all this bloat to our test code…
public static CreateTableRequest createTableStructureFor(Class<?> entityClass) {
checkSupportedEntityClass(entityClass);
DynamoDBTable entityTable = entityClass.getAnnotation(DynamoDBTable.class);
CreateTableRequest tt = new CreateTableRequest();
tt.setTableName(entityTable.tableName());
tt.setBillingMode("PAY_PER_REQUEST");
tt.setAttributeDefinitions(new ArrayList<>());
tt.setKeySchema(new ArrayList<>());
for (Field f : entityClass.getDeclaredFields()) {
if (f.isAnnotationPresent(DynamoDBHashKey.class)) {
AttributeDefinition a = parseFieldAttribute(f);
tt.getAttributeDefinitions().add(a);
tt.getKeySchema().add(new KeySchemaElement(a.getAttributeName(), KeyType.HASH));
} else if (f.isAnnotationPresent(DynamoDBRangeKey.class)) {
AttributeDefinition a = parseFieldAttribute(f);
tt.getAttributeDefinitions().add(a);
tt.getKeySchema().add(new KeySchemaElement(a.getAttributeName(), KeyType.RANGE));
}
}
return tt;
}I normally hate reflection, but doing it with annotations doesn’t feel quite as dirty to me as it has when I’ve used it in the past. This is the first time I’ve done any serious work with annotations rather than letting an Application Container or Spring deal with them, but so far so good.
We can do a much simpler style of this to delete the table too:
public static void deleteTableFor(AmazonDynamoDB connection, Class<?> entityClass) {
DynamoDBTable entityTable = entityClass.getAnnotation(DynamoDBTable.class);
if (connection.listTables().getTableNames().contains(entityTable.tableName())) {
connection.deleteTable(entityTable.tableName());
}
}Ok, we’ve started to build a set of utilities that will be of use for our next post, injecting datasets into our container.
I hope this goes without saying, but I’m going to say it anyway, this is:
THIS IS ALL ABOUT TESTING!
DONT DO THIS IN PRODUCTION
Use CloudFormation!
Check out the other posts in this series I’ll come back and provide the links to the follow up posts here once they’ve been updated: