Search code examples
javadatetimeamazon-web-servicesamazon-dynamodbobject-persistence

Using joda DateTime Range Key with AWS DynamoDB Object Persistence model


I've got a dynamodb table with a timestamp ("creationDate") as a range. The model is using a joda DateTime to make it easy to use (compatibility with the rest of the code). To be able to make between queries on this range, I used a numeric type for the attribute in the table, and planned to store it as a java timestamp (milliseconds since epoch). Then, I added a marshaller to convert a joda DateTime to a String representing a long and vice-versa.

The table structure (creation):

void CreateTable()
{
    CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(LinkManager.TABLE_NAME);

    ProvisionedThroughput pt = new ProvisionedThroughput()
        .withReadCapacityUnits(LinkManager.READ_CAPACITY_UNITS)
        .withWriteCapacityUnits(LinkManager.WRITE_CAPACITY_UNITS);

    createTableRequest.setProvisionedThroughput(pt);

    ArrayList<AttributeDefinition> ad = new ArrayList<AttributeDefinition>();

    ad.add(new AttributeDefinition().withAttributeName("creationDate").withAttributeType(ScalarAttributeType.N));
    ad.add(new AttributeDefinition().withAttributeName("contentHash").withAttributeType(ScalarAttributeType.S));

    createTableRequest.setAttributeDefinitions(ad);

    ArrayList<KeySchemaElement> ks = new ArrayList<KeySchemaElement>();
    ks.add(new KeySchemaElement().withAttributeName("contentHash").withKeyType(KeyType.HASH));
    ks.add(new KeySchemaElement().withAttributeName("creationDate").withKeyType(KeyType.RANGE));

    createTableRequest.setKeySchema(ks);

    this.kernel.DDB.createTable(createTableRequest);
}

The model:

@DynamoDBTable(tableName="Link")
public class Link {
    private String ContentHash;
    private DateTime CreationDate;

    @DynamoDBHashKey(attributeName = "contentHash")
    public String getContentHash() {
        return ContentHash;
    }
    public void setContentHash(String contentHash) {
        ContentHash = contentHash;
    }

    @DynamoDBRangeKey(attributeName = "creationDate")
    @DynamoDBMarshalling(marshallerClass = DateTimeMarshaller.class)
    public DateTime getCreationDate() {
        return CreationDate;
    }
    public void setCreationDate(DateTime creationDate) {
        CreationDate = creationDate;
    }
}

The marshaller:

public class DateTimeMarshaller extends JsonMarshaller<DateTime>
{
    public String marshall(DateTime dt)
    {
        return String.valueOf(dt.getMillis());
    }

    public DateTime unmarshall(String dt)
    {
        long ldt = Long.parseLong(dt);
        return new DateTime(ldt);
    }
}

I get the following error:

Exception in thread "main" com.amazonaws.AmazonServiceException: Type of specified attribute inconsistent with type in table (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: 8aabb703-cb44-4e93-ab47-c527a5aa7d52)

I guess this is because the marshaller returns a String, and dynamoDB wants a numeric type, as the attribute type is N. I don't know what people do in this case, I searched for a solution but couldn't find it. I only tested this on a local dynamodb instance, which I don't think makes any difference (this is a validation check failing, there's no request even made).

The obvious workaround is to use long type for the dates in the model and add special getters and setters to work with DateTime. Still, is there a cleaner way ? I am sure I'm not the only one using DatTime range in a model.


Solution

  • What I would do is re-create the table with the Range key as String itself. Even if it's going to be populated with long numbers, making it type: S will ensure compatibility with the Marshaller