At re:Invent 2018 AWS has released Amazon DynamoDB Support for Transactions. DynamoDB transactions is described in the AWS News Blog. Transactions provide atomicity, consistency, isolation, and durability (ACID) in DynamoDB, enabling you to maintain data correctness in your applications more easily. Using transactions, you can support sophisticated workflows and business logic that require adding, updating, or deleting multiple items as a single, all-or-nothing operation. Lets take a look!
New Operations
To support DynamoDB transactions, AWS has added two new operations to the API:
- TransactWriteItems: a batch operation that contains a write set, with one or more PutItem, UpdateItem, and DeleteItem operations. TransactWriteItems can optionally check for prerequisite conditions that must be satisfied before making updates. These conditions may involve the same or different items than those in the write set. If any condition is not met, the transaction is rejected.
- TransactGetItems: a batch operation that contains a read set, with one or more GetItem operations. If a TransactGetItems request is issued on an item that is part of an active write transaction, the read transaction is canceled. To get the previously committed value, you can use a standard read.
Example
The following example uses the Go API and writes to the table Owner
and Cats
using the TransactWriteItems API. After a succesful write, a TransactGetItems API call reads from the same two tables and writes the result to the console.
package main
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
"log"
)
type Cat struct {
ID string
Name string
Age int
OwnerID string
}
type Owner struct {
ID string
Name string
Age int
CatIds []string
}
func CreateDynamoDBClient(region string) (*dynamodb.DynamoDB, error) {
sess, err := session.NewSession()
if err != nil {
return nil, err
}
svc := dynamodb.New(sess, aws.NewConfig().WithRegion(region))
return svc, nil
}
func main() {
owner := Owner{
ID: "1",
Name: "Dennis",
Age: 42,
CatIds: []string{"1", "2"},
}
elsa := Cat{
ID: "1",
Name: "Elsa",
Age: 16,
OwnerID: "1",
}
tijger := Cat{
ID: "2",
Name: "Tijger",
Age: 12,
OwnerID: "1",
}
OwnerAV, err := dynamodbattribute.MarshalMap(owner)
if err != nil {
log.Fatal(err)
}
ElsaAV, err2 := dynamodbattribute.MarshalMap(elsa)
if err2 != nil {
log.Fatal(err)
}
TijgerAV, err3 := dynamodbattribute.MarshalMap(tijger)
if err != nil {
log.Fatal(err3)
}
svc, err := CreateDynamoDBClient("eu-west-1")
if err != nil {
log.Fatal(err)
}
_, err4 := svc.TransactWriteItems(&dynamodb.TransactWriteItemsInput{
TransactItems: []*dynamodb.TransactWriteItem{
{
Put: &dynamodb.Put{
TableName: aws.String("Owners"),
Item: OwnerAV,
},
},
{
Put: &dynamodb.Put{
TableName: aws.String("Cats"),
Item: ElsaAV,
},
},
{
Put: &dynamodb.Put{
TableName: aws.String("Cats"),
Item: TijgerAV,
},
},
},
})
if err != nil {
log.Fatal(err4)
}
res, err5 := svc.TransactGetItems(&dynamodb.TransactGetItemsInput{
TransactItems: []*dynamodb.TransactGetItem{
{
Get: &dynamodb.Get{
TableName: aws.String("Cats"),
Key: map[string]*dynamodb.AttributeValue{
"ID": {
S: aws.String("1"),
},
},
},
},
{
Get: &dynamodb.Get{
TableName: aws.String("Cats"),
Key: map[string]*dynamodb.AttributeValue{
"ID": {
S: aws.String("2"),
},
},
},
},
{
Get: &dynamodb.Get{
TableName: aws.String("Owners"),
Key: map[string]*dynamodb.AttributeValue{
"ID": {
S: aws.String("1"),
},
},
},
},
},
})
if err != nil {
log.Fatal(err5)
}
for _, item := range res.Responses {
fmt.Print(item.Item)
}
}
Conclusion
DynamoDB Transactions is provided by the operations TransactWriteItems and TransactGetItems. By having ACID guarantees, DynamoDB can be used to keep two or more region-tables consistent and opens the way for new cloud scale data architectures using DynamoDB.