0

I am new to AWS in general, I am building a relatively simple application with Amplify, but I've used Google Firebase before. My question is: Is there a way to set a constrain for a field to be non-negative? I have an application that does transactions and I don't want my balance to be negative. I just need a simple error/exception. Is it possible to set a field constraint in DynamoDB that says "This field should be >= 0"?.

I also checked if it was possible to do it in the VTL amplify generated resolver of my graphql mutation, and indeed it is possible to set some constraints, But somehow it allows the operation and crashes on the next one (when the balance on the DB is already < 0, like if it checks it before the update). I tried saying something like "current_balance - transaction >= 0" but I couldn't get it to work.

So it seems that the only way is to create a custom lambda resolver that does the various checks before submitting the mutation to DynamoDB. I haven't tried it yet but I don't understand how I can do a check on the current balance (stored in the DB) without doing a query.

More in general is it even possible to validate fields (even with simple assertions like non-negative) on amplify/dynamoDB? Moving to another DB like Aurora would help?

Thanks for you help

1 Answers1

1

DynamoDb supports conditional updates which allow an update to be applied when the given condition is met. You can set the condition current_balance >= cost for your update.

However, the negative balance is not the main problem. What you should address is how to prevent other requests from updating the same current_balance at the same time, or in short, race conditions on current_balance. In order to deal with that, you also need a conditional update whose condition is "current_balance = initial_balance". The initial_balance is, I guess, what you get from DynamoDB at the very beginning of the purchase process.

Sample VTL code

#set( $remaining_balance = $initial_balance - $transaction_cost )
#if( $remaining_balance < 0 )
  $util.error("Insufficient balance")
#end

{
  "version" : "2018-05-29",
  "operation" : "UpdateItem",
  "key": { <your-dynamodb-key>  },
  "update" : {
    "expression" : "SET current_balance = :remaining_balance",
    "expressionValues" : {
      ":remaining_balance" : $util.dynamodb.toNumberJson($remaining_balance)
    }
  },
  "condition": {
    "expression": "current_balance = :initial_balance",
    "expressionValues" : {
      ":initial_balance" : $util.dynamodb.toNumberJson($initial_balance)
    }
  }
}
Hung Tran
  • 1,595
  • 2
  • 17
  • 17
  • Thank you for your answer! How do I get $initial_balance? (which is what is currently in the DB) From what I understood, I have to do a query first to get the current balance (but this way raises all kinds of race conditions) and it's just inefficient. I tried conditional updated setting ":balance >= 0" but it let the operation happen and blocks the next one (where the balance is already 0) So I guess I have to set it to ":balance >= cost" right? – valerioneri Apr 13 '21 at 03:15
  • "balance >= 0" doesn't make sure if the current balance is < cost. As a result, the update can be applied, and it leads to a negative balance. "balance >= cost" is the right condition. – Hung Tran Apr 13 '21 at 03:42
  • $initial_balance is retrieved by another Db query. I'd prefer a conditional update over the atomic counter, SET current_balance = current_balance - cost . The problem with atomic counters is they can be executed twice. You can use atomic counters where you can afford inaccuracy like visitor counts. Otherwise, you're better off with conditional updates. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.AtomicCounters – Hung Tran Apr 13 '21 at 04:09