amazon-dynamodb

DynamoDB transactions


I'm reading about DynamoDB transactions and it says:

You can't target the same item with multiple operations within the same transaction. For example, you can't perform a ConditionCheck and also an Update action on the same item in the same transaction.

However, when you go to the DynamoDB transaction examples, I see:

Update markItemSold = new Update()
    .withTableName(PRODUCT_TABLE_NAME)
    .withKey(productItemKey)
    .withUpdateExpression("SET ProductStatus = :new_status")
    .withExpressionAttributeValues(expressionAttributeValues)
    .withConditionExpression("ProductStatus = :expected_status")
    .withReturnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD);

So which is it? Is it that case that "you can't perform a ConditionCheck and also an Update action on the same item in the same transaction" or not?


Solution

  • You're conflating two different aspects of conditional checks.

    In TransactWriteItems, there are 4 API requests that you can make:

    Each of these are different entities.

    Within each of these API's you can apply a ConditionExpression which is what you highlight in your code. Your code is a single Update operation which includes a ConditionExpression, which is different to using a ConditionCheck.

    This is fine:

    Update markItemSold = new Update()
        .withTableName(PRODUCT_TABLE_NAME)
        .withKey(productItemKey)
        .withUpdateExpression("SET ProductStatus = :new_status")
        .withExpressionAttributeValues(expressionAttributeValues)
        .withConditionExpression("ProductStatus = :expected_status")
        .withReturnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD);
    

    This is not fine, as its two operations on the same item:

    Update markItemSold = new Update()
        .withTableName(PRODUCT_TABLE_NAME)
        .withKey(productItemKey)
        .withUpdateExpression("SET ProductStatus = :new_status")
        .withExpressionAttributeValues(expressionAttributeValues)
        .withReturnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD);
    
    ConditionCheck checkProductStatus = new ConditionCheck()
        .withTableName(PRODUCT_TABLE_NAME)
        .withKey(productItemKey)
        .withConditionExpression("ProductStatus = :expected_status")
        .withExpressionAttributeValues(expressionAttributeValues);
    

    An example of when to use ConditionCheck in a transaction:

    Imagine you are an event promoter and each event has a finite number of tickets which users can purchase. You have two tables, one to hold the available tickets per event, the other to vend tickets to users who purchase them:

    CAPACITY_TABLE |PK| Tickets ---|--- Event#123 | 33 Event#203 | 221

    PURCHASE_TABLE |PK| SK | TicketsPurchased ---|---|--- User001 | Event#123 | 2 | User302 | Event#123 | 2 | User291 | Event#203 | 1 |

    Now User333 comes to purchase two tickets for Event#123. To do it all in a single atomic request, we can use transactions, one Put to purchase the tickets, and one ConditionCheck to make sure we have available capacity:

    // Purchase ticket
    Put purchase = new Put()
        .withTableName(PURCHASE_TABLE)
        .withItem(eventTickets)
    
    // Check availability
    ConditionCheck checkCapacity = new ConditionCheck()
        .withTableName(CAPACITY_TABLE)
        .withKey(eventKey)
        .withConditionExpression("Tickets > :purchased")
        .withExpressionAttributeValues(expressionAttributeValues);