Search This Blog

Thursday 4 June 2020

Dynamo Db's API Model

While doing a search for Dynamo Db get Item code, I came across several different code samples (obviously) and also realized a not so obvious thing - there are multiple ways to access Dynamo Db through code.
No longer confident on what is the best way, I decided to check the java docs. Turns out, there are (as of today) 3 ways to perform CRUD operations on a Dynamo Table. This link gives a very nice description of the same. I decided to mess around with the available options. As a prerequisite, I setup a simple users table with a hash key.

Technique 1: Use low-level interface
The low-level interface for Amazon DynamoDB most closely resemble the POST requests used for Dynamo operations. The below code will convert into a POST request and create an Item in the table.
    private static void createItemUsingLowLevelApi(List<String> hobbies,
                                int suffix,
                                AWSCredentialsProvider awsCredentialsProvider) {
        AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
                .withRegion("us-east-1")
                .withCredentials(awsCredentialsProvider).build();
        PutItemRequest request = new PutItemRequest().withTableName("Users")
                .withReturnConsumedCapacity("TOTAL");
        request.addItemEntry("userId", new AttributeValue("userId_" + suffix))
                .addItemEntry("name", new AttributeValue("User_" + suffix))
                .addItemEntry("age", new AttributeValue().withN("25"))
                .addItemEntry("hobbies", new AttributeValue(hobbies));

        request.setConditionExpression("attribute_not_exists(userId)");
        request.setReturnValues(ReturnValue.ALL_OLD);
        request.withReturnItemCollectionMetrics(
                       ReturnItemCollectionMetrics.SIZE);

        PutItemResult response = client.putItem(request);
        System.out.println(response);
    
AmazonDynamoDB class implements the DynamoDB low-level interface.  The userId is the primary hash key here. The Interface assumes the data type is string unless explicitly specified.
The response for the code is:
{ConsumedCapacity: {TableName: Users,CapacityUnits: 1.0,},}
PutItem will override the entry if one exists in the table with same Range key. That is the reason the API returns the OLD value.
If we do not want this behavior than we can add the condition expression that inserts record only if one with userId does not exists.

Technique 2: Use Document Interfaces

private static void createItemUsingDocumentApi(List<String> hobbies,
                                int suffix,
                                AWSCredentialsProvider awsCredentialsProvider) {
        AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
                .withRegion("us-east-1")
                .withCredentials(awsCredentialsProvider).build();
        DynamoDB docClient = new DynamoDB(client);

        Table table = docClient.getTable("Users");
        
        Item item = new Item().withPrimaryKey("userId", "userId_" + suffix)
                .withString("name", "User_" + suffix)
                .withStringSet("hobbies", new HashSet<>(hobbies))
                .withNumber("age", 25);
        PutItemOutcome putItemOutcome = table.putItem(item,
                           new Expected("userId").notExist());                
        System.out.println(putItemOutcome);
    }
The code uses DynamoDB instance which is a wrapper around the AmazonDynamoDB instance. Table and Item are representations of the dynamo db table and table entry.
The not exists constraint is added through the Expected instance passed to putItem method. The datatype of the attributes is implied from the method used to add them (unlike in LowLevel where it needs to be specified)
. The output to console came as blank
{}
From the AWS Docs:
Many AWS SDKs provide a document interface, allowing you to perform data plane
operations (create, read, update, delete) on tables and indexes. 
With a document interface, you do not need to specify Data Type 
Descriptors. The data types are implied by the semantics of the data itself.
These AWS SDKs also provide methods to easily convert JSON documents to 
and from native Amazon DynamoDB data types.

Technique 3: Use Object Persistence Interface
Some AWS SDKs provide an object persistence interface where you do not directly 
perform data plane operations. Instead, you create objects that represent items in Amazon DynamoDB tables and indexes, and 
interact only with those objects. This allows you to write object-centric code, rather than database-centric code.

    private static void createItemUsingObjectPersistenceApi(
            List<String> hobbies, int suffix,
            AWSCredentialsProvider awsCredentialsProvider) {
        AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
                .withRegion("us-east-1")
                .withCredentials(awsCredentialsProvider).build();

        User user = new User();
        user.setUserId("userId_" + suffix);
        user.setUserName("User_" + suffix);
        user.setAge(25);
        user.setHobbies(new HashSet<>(hobbies));

        DynamoDBMapper mapper = new DynamoDBMapper(client);
        Map<String, ExpectedAttributeValue> expected = new HashMap<>();
        expected.put("userId", new ExpectedAttributeValue().withExists(false));
        DynamoDBSaveExpression saveExpression = new DynamoDBSaveExpression().withExpected(expected);
        mapper.save(user, saveExpression);// void method
    } 
The code uses a DynamoDBMapper instance the which is the object persistence interface in java SDK. The not exists condition is achieved using the DynamoDBSaveExpression. DynamoDBMapper is a wrapper around the AmazonDynamoDB class.
The User class is as below:
@DynamoDBTable(tableName = "Users")
public class User {

    @DynamoDBHashKey
    private String userId;
    @DynamoDBAttribute(attributeName = "name")
    private String userName;
    private Set<String> hobbies;
    private Integer age;
    //setters and getters
}
This is similar to Hibernate annotations. The user pojo needs the DynamoDBTable and DynamoDBHashKey annotations. The DynamoDBAttribute is optional, in this case my db column name deferred from the POJO attribute name.

These were the three options provided in Java SDK for Dynamo Db operations. All of these translate to POST requests executed on Dynamo endpoints.
The Amazon DynamoDB low-level API is the protocol-level interface for DynamoDB. 
At this level, every HTTP(S) request must be correctly formatted and carry a 
valid digital signature.
The AWS SDKs construct low-level DynamoDB API requests on your behalf and process
the responses from DynamoDB. This lets you focus on your application logic, 
instead of low-level details.

No comments:

Post a Comment