amazon-web-servicesgoamazon-dynamodb

Checking the existence of parent item before putting linked items in AWS DynamoDB


One order can contain one or many products. Also an order belongs to a customer. The order, product and customer are our item models in DB. A new order creation accepts a request like below.

{
    "customer_id": "c8fb0e64-43b0-45b1-a9e7-fe2ce61c2c1f",
    ...
    "products": [
        {
            "id": "7362ea92-2063-48ad-9bfe-c91a5d9af4a6",
            ...
        },
        {
            "id": "83e38b2f-2839-43c5-a834-0834d841e566",
            ...
        }
    ]
}

When I create a new order, I use TransactWriteItems operation which works fine so far (see the image below). However, at the moment I am not checking the existence of:

  1. customer (c8fb0e64-43b0-45b1-a9e7-fe2ce61c2c1f).
  2. products (7362ea92-2063-48ad-9bfe-c91a5d9af4a6 and 83e38b2f-2839-43c5-a834-0834d841e566)

when putting the items in DB.

As you can see in the image, customer (the one at the bottom) and product items (two on top) are already in same table. A customer is identifiable by part_key and/or customer_id attributes while a product is identifiable by part_key and/or product_id attributes. Also each items in table has a _type attribute to express what items they are.

enter image description here

The question is, how do we in general manage existence check before inserting a new item in DB with transactions? I am aware of attribute_exists and conditional checks but not sure about the right approach as far as the performance and cost considerations go.

Here's is my code for the reference purposes if it helps.

func (d DynamoDB) CreateOrder(ctx context.Context, model database.Order) error {
    items := make([]types.TransactWriteItem, 0, len(model.Products)+1)

    for _, product := range model.Products {
        product.Key = database.Key{
            PartKey: "order#" + product.OrderID,
            SortKey: "product#" + product.ID,
        }
        product.Type = "order_product"

        item, err := attributevalue.MarshalMap(product)
        if err != nil {
            return err
        }

        items = append(items, types.TransactWriteItem{
            Put: &types.Put{
                TableName: aws.String(d.Table),
                Item:      item,
            },
        })
    }

    model.Key = database.Key{
        PartKey: "order#" + model.ID,
        SortKey: "order#" + model.ID,
    }
    model.Type = "order"

    item, err := attributevalue.MarshalMap(model)
    if err != nil {
        return err
    }

    items = append(items, types.TransactWriteItem{
        Put: &types.Put{
            TableName: aws.String(d.Table),
            Item:      item,
        },
    })

    _, err = d.Client.TransactWriteItems(ctx, &dynamodb.TransactWriteItemsInput{
        TransactItems: items,
    })
    if err != nil {
        return err
    }

    return nil
}

Solution

  • attribute_not_exists () with condition expression can be used to check for item existence. There are no extra costs to adding condition expressions. However, even if your condition fails you will be charged for the write operation (put/transact) in the same manner as it would have cost in case of a successful write.

    Another way to to avoid this extra cost is to simply do a get request to check for existence as the read would cost less. Dynamodb WCU-RCU