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:

  • Add the Table Structures through API calls
  • Setup the container with a CloudFormation Script

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:

Table Structures through API calls

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:

  • TableName
    • The name of our table structure
  • Key Schema
    • Defines the key structures for the DynamoDb table that will act as our lookup values
    • In this example, I’m using a simple Hash key to identify the customer
  • Atttribute Definitions
    • Defines the data type for our key field
    • As DynamoDb doesn’t need to define any data structure other than its Key value that’s all we need
  • Billing Mode
    • This is a required attribute for DynamoDb, in most of your cases, PAY_PER_REQUEST, will be fine for local stack testing, you shouldn’t need to worry about provisioning.
    • If you do come across cases where this does need declared upfront as a unit test, let me know, I’d be curious about them.

But I don’t want to write this every time, that’s going to get old quickly… and I’m a lazy developer.

Processing Annotations

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…

Processing an entity for the meta data for our table

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:

Deleting a table for an entity

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:

  1. Development with AWS & LocalStack
  2. Unit Testing Java and DyanmoDB, with JUnit & LocalStack
  3. Configuration through Annotation (This Post)
  4. Building a @Dataset

Find me on ...