DynamoDB - How to implement Optimistic Locking using ServiceStack.Aws

Viewed 215

Currently, I am using ServiceStack.Aws v5.9.0 to communicate with DynamoDB. I have used PutItem for both creating and updating an item without anticipating data loss in case of concurrency handling.

public class Customer
{
   [HashKey]
   public int CustomerId { get; set; }
   [AutoIncrement]
   public int SubId { get; set; }
   public string CustomerType { get; set; }
   public string LastName { get; set; }
   public string FirstName { get; set; }
   ...//and hundreds of fields here
}

public class CustomerDynamo
{
   private readonly IPocoDynamo db;
   //Constructor
   public CustomerDynamo() 
   {
      var dynamoClient = new AmazonDynamoDBClient(_region);
      var entityType = typeof(Customer);
      var tableName = entityType.Name;
      entityType.AddAttributes(new AliasAttribute(name: tableName));
      db = new PocoDynamo(dynamoClient) { ConsistentRead = true }.RegisterTable(tableType: entityType);
   }

   public Customer Update(Customer customer)
   {
      customer.ModifiedDate = DateTime.UtcNow;
      db.PutItem(customer);
      return customer;
   }
}

The above Update method is called in every service/async task that needs to update the data of the customer. Refer to this article of AWS I decided to implement the Optimistic Locking to save my life from the issue of concurrency requests. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBContext.VersionSupport.html

Assume that the VersionNumber will be the key for Optimistic Locking. So I added the VersionNumber into the Customer model.

public class Customer
{
   [HashKey]
   public int CustomerId { get; set; }
   [AutoIncrement]
   public int SubId { get; set; }
   public string CustomerType { get; set; }
   public string LastName { get; set; }
   public string FirstName { get; set; }
   ...//and hundreds of fields here
   [DynamoDBVersion]
   public int? VersionNumber { get; set; }
}
  1. The result is VersionNumber not updated while it should be automatically incremented. I think it is just because the PutItem will override the whole existing item. Is this correct?
  2. I think I need to change from PutItem to UpdateItem in the Update method. The question is how can I generate the expression dynamically to be used with the UpdateItem?

Thanks in advance for any help!

Updates:

Thanks @mythz for the useful information about DynamoDBVersion attribute. Then I tried to remove the DynamoDBVersion and using the UpdateExpression of PocoDynamo as below

public Customer Update(Customer customer)
{
   customer.ModifiedDate = DateTime.UtcNow;
   var expression = db.UpdateExpression<Customer>(customer.CustomerId).Set(() => customer);
   expression.ExpressionAttributeNames = new Dictionary<string, string>() 
   { 
      { "#Version", "VersionNumber" } 
   };
   expression.ExpressionAttributeValues = new Dictionary<string, AttributeValue>() 
   {
      { ":incr", new AttributeValue { N = "1" } },
      { ":zero", new AttributeValue { N = "0" } }
   };
   expression.UpdateExpression = "SET #Version = if_not_exists(#Version, :zero) + :incr";
   if (customer.VersionNumber.HasValue)
   {
      expression.Condition(c => c.VersionNumber == customer.VersionNumber);
   }
   var success = db.UpdateItem(expression);
}

But the changes are not saved except the VersionNumber

1 Answers

The [DynamoDBVersion] is an AWS Object Persistence Model attribute for usage with AWS's DynamoDBContext not for PocoDynamo. i.e. the only [DynamoDB*] attributes PocoDynamo utilizes are [DynamoDBHashKey] and [DynamoDBRangeKey] all other [DynamoDB*] attributes are intended for AWS's Object Persistence Model libraries.

When needed you can access AWS's IAmazonDynamoDB with:

var db = new PocoDynamo(awsDb);
var awsDb = db.DynamoDb;

Here are docs on PocoDynamo's UpdateItem APIs that may be relevant.

Related