Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Migration details for DynamoDB v2 in AWS Java SDK?

Has anyone made the change to the new namespaces (com.amazonaws.services.dynamodbv2) and interfaces for DynamoDB in the AWS Java SDK 1.4.2 (and later)? The release of Local Secondary Indices apparently necessitated breaking changes as per the 1.4.2 release notes.

Has anyone found a guide detailing what changed and what needs to happen to migrate existing code? I am trying to decide when is best to make this change for an existing code base.

like image 453
mckamey Avatar asked May 17 '13 15:05

mckamey


2 Answers

The new dynamodbv2 namespace of DynamoDB introduces the following incompatible changes (in that they are not simply additive, and require code changes to switch to the new namespace):

  • HashKeyElement and RangeKeyElement are replaced with a Map<String, AttributeValue>. This includes the structures named ExclusiveStartKey, LastEvaluatedKey, and Key. The main impact on the code with this change is that now in order to call GetItem, for example, your code needs to know the attribute names of the primary keys, and not just the primary key values.
  • Query now uses a KeyCondition of type Map<String, Condition> to specify the full query, instead of having separate HashKeyValue and RangeKeyCondition fields.
  • CreateTable input separates the attribute type definitions from the primary key definitions (and create/update/delete/describe responses match this)
  • Consumed Capacity in responses is now a structure instead of a single number, and must be asked for in the request. In Batch operations, this is returned in a separate ConsumedCapacity structure, instead of alongside the results.

It is possible to migrate your code to the new Java API incrementally, if desired. If you plan to add functionality to your code that queries Local Secondary Indexes, or creates tables with local secondary indexes, you will need to use the new API for that part of your code.

If you create a table with Local Secondary Indexes with the new API, you can still use your existing code in the dynamodb namespace to perform all of the existing operations on that table. As an example, PutItem with the dynamodb namespace client will work against tables created using the dynamodbv2 client, as well as the other way around.

like image 136
David Yanacek Avatar answered Nov 15 '22 16:11

David Yanacek


DynamoDB AWS Java 1.4.1 => 1.4.2 (non-exhaustive) migration steps

Well, I bit the bullet and did it. Here's my experience.

First, change the DynamoDB namespace:

  • com.amazonaws.services.dynamodb => com.amazonaws.services.dynamodbv2

The first thing you notice is there are types missing. The most major is Key is gone. Good riddance as it was too generic of a name. It is now replaced with Map which makes sense as keys become a lot more malleable with Local Secondary Indexes (LSI). Unfortunately, working with maps and generics in general sucks in Java (see Bonus at bottom). Gone are the fluent interfaces of withHashKeyElement / withRangeKeyElement.

Next, very carefully find/replace the DynamoDB types which were replaced by generic collections:

  • com.amazonaws.services.dynamodb.model.Key => Map<String, AttributeValue>
  • com.amazonaws.services.dynamodb.model.BatchResponse => List<Map<String, AttributeValue>>
  • com.amazonaws.services.dynamodb.model.KeySchema => List<KeySchemaElement>
  • (Probably others, these were the few I directly referenced.)

Next, find everything that broke. This is a very manual process that requires knowing your code base and the SDK quite well. Specifically, you must know your key schema very well as it's all strings from here on. Luckily in my situation, the first two tasks were about 90% of the changes:

  • Find every new Map<String, AttributeValue>, it is an indication that Key was formerly there.
  • QueryRequest merged withHashKeyValue(AttributeValue) withRangeKeyCondition(Condition) into the mega withKeyConditions(Map<String,Condition>) This method is the core change for LSI lets you specify things other than hash/range. What it accepts is more restrictive than the interface, but this is logical when you consider DynamoDB only lets you query indexed attributes.
  • DynamoDBQueryExpression became generic and changed its interface (not sure why).
  • KeySchemaElement no longer needs AttributeType, but now needs KeyType

Finally, Compile and regression test your entire stack.

P.S. While I was performing all this, version 1.4.4.1 was just released to Maven Central.


Bonus

Since Maps are the common solution to Java's lack of loosely typed classes, they are everywhere. A little helper lib can really go a long way toward building these less verbosely. Here are some bits of my helper:

public class MakeJavaSuckLess { // TODO: find a shorter class name
    public static final float MAX_LOAD_FACTOR = 1.0f;

    /**
     * Builds a mutable Map from an interlaced sequence of key-value pairs
     * where keys are strings and values are VType objects
     * @param pairs
     * @return
     */
    public static <VType> Map<String, VType> asMap(Object... pairs) {
        return mergeMaps(null, pairs);
    }

    /**
     * Builds a mutable Map from an interlaced sequence of key-value pairs
     * where keys are strings and values are VType objects
     * @param pairs
     * @return
     */
    public static <VType> Map<String, VType> mergeMaps(Map<String, VType> map, Object... pairs) {
        final int length = (pairs != null) ? pairs.length/2 : 0;
        if (map == null) {
            // max out the load factor since most of these don't change
            map = new HashMap<String, VType>(length, MAX_LOAD_FACTOR);
        }

        for (int i=0; i<length; i++) {
            String key = asString(pairs[2*i]);
            @SuppressWarnings("unchecked")
            VType value = (VType)pairs[2*i+1];
            map.put(key, value);
        }
        return map;
    }
}

Now creating your DynamoDB keys is slightly less ugly:

Map<String, AttributeValue> key = MakeJavaSuckLess.asMap("hashKey", new AttributeValue("hashVal"), "rangeKey", new AttributeValue("rangeVal"));

Type erasure in Java's generics makes this again uglier in places. You will sometimes need to explicitly specify VType:

new GetItemRequest().withKey(MakeJavaSuckLess.<AttributeValue>asMap("hashKey", new AttributeValue("hashVal"), "rangeKey", new AttributeValue("rangeVal")));
like image 41
mckamey Avatar answered Nov 15 '22 16:11

mckamey