dynamodb

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package dynamodb keeps idempotency records in Amazon DynamoDB: the persistence layer Powertools Python calls DynamoDBPersistenceLayer, with the same item schema and the same conditional writes, so that Go and Python functions can share a table.

Table

One table can serve every idempotent function. Unless the attributes are renamed, it needs:

  • partition key "id", a string;
  • time to live on "expiration", so that DynamoDB removes expired records (it may take up to 48 hours: records carry their own expiry, which is what counts).

The function needs dynamodb:GetItem, dynamodb:PutItem, dynamodb:UpdateItem and dynamodb:DeleteItem on it.

Usage

cfg, err := config.LoadDefaultConfig(ctx)
if err != nil { ... }
store, err := dynamodb.FromConfig(cfg, os.Getenv("IDEMPOTENCY_TABLE"))
if err != nil { ... }
lambda.Start(idempotency.Idempotent(handle, store))

New takes any Client, the four methods of *dynamodb.Client the store calls: a client with a custom endpoint (DynamoDB Local), retryer or middleware, or a fake in tests.

Items

Attribute               Type  Content
id                      S     the idempotency key, or the static partition key value
(sort key)              S     the idempotency key, on tables with a composite key
expiration              N     when the record expires, epoch seconds
in_progress_expiration  N     when an in-progress record times out, epoch milliseconds
status                  S     INPROGRESS or COMPLETED
data                    S     the function's result as JSON, on COMPLETED records
validation              S     the payload validation hash, when validation is on

Every name can be changed: WithKeyAttr, WithExpiryAttr, WithInProgressExpiryAttr, WithStatusAttr, WithDataAttr, WithValidationKeyAttr.

A table with a composite primary key (WithSortKeyAttr) stores the idempotency key in the sort key, under a partition key of "idempotency#<function name>" (AWS_LAMBDA_FUNCTION_NAME), or the value of WithStaticPKValue.

Requests

A first call costs a conditional PutItem and an UpdateItem (2 WCU); a failed function a DeleteItem. The PutItem succeeds when the key is free, expired, or held by an in-progress record whose invocation timed out:

attribute_not_exists(#id) OR #expiry < :now OR
(#status = :inprogress AND attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now_in_millis)

When it fails, DynamoDB returns the item that holds the key (ReturnValuesOnConditionCheckFailure ALL_OLD), so a replay costs no read. Records must fit the 400 KB item limit, response included.

Differences from Python

Records read back from a table with a composite key keep their idempotency key; Python takes the static partition key for it, which keeps them out of its local cache. Boto3 sessions and configs are an aws.Config here, and the user agent tag is not added.

Index

Examples

Constants

View Source
const (
	DefaultKeyAttr              = "id"
	DefaultExpiryAttr           = "expiration"
	DefaultInProgressExpiryAttr = "in_progress_expiration"
	DefaultStatusAttr           = "status"
	DefaultDataAttr             = "data"
	DefaultValidationKeyAttr    = "validation"
)

The attribute names Powertools Python uses unless told otherwise.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client interface {
	GetItem(ctx context.Context, params *awsdynamodb.GetItemInput, optFns ...func(*awsdynamodb.Options)) (*awsdynamodb.GetItemOutput, error)
	PutItem(ctx context.Context, params *awsdynamodb.PutItemInput, optFns ...func(*awsdynamodb.Options)) (*awsdynamodb.PutItemOutput, error)
	UpdateItem(ctx context.Context, params *awsdynamodb.UpdateItemInput, optFns ...func(*awsdynamodb.Options)) (*awsdynamodb.UpdateItemOutput, error)
	DeleteItem(ctx context.Context, params *awsdynamodb.DeleteItemInput, optFns ...func(*awsdynamodb.Options)) (*awsdynamodb.DeleteItemOutput, error)
}

Client is the part of the DynamoDB API the store calls; *dynamodb.Client from aws-sdk-go-v2 satisfies it.

type Option

type Option func(*Store)

Option configures a Store.

func WithDataAttr

func WithDataAttr(name string) Option

WithDataAttr names the attribute of the stored response, "data" by default.

func WithExpiryAttr

func WithExpiryAttr(name string) Option

WithExpiryAttr names the attribute of the record's expiry, in epoch seconds; it defaults to "expiration", which is the one to make the table's TTL attribute.

func WithInProgressExpiryAttr

func WithInProgressExpiryAttr(name string) Option

WithInProgressExpiryAttr names the attribute of an in-progress record's expiry, in epoch milliseconds; it defaults to "in_progress_expiration".

func WithKeyAttr

func WithKeyAttr(name string) Option

WithKeyAttr names the partition key attribute, "id" by default.

func WithSortKeyAttr

func WithSortKeyAttr(name string) Option

WithSortKeyAttr names the sort key attribute of a table with a composite primary key. The idempotency key then goes in the sort key, and the partition key holds the static value of WithStaticPKValue.

Example
// A table whose primary key is "id" and "sort_key"; a fake one here.
table := newFakeDynamoDB("id", "sort_key")
store, err := dynamodb.New(table, "IdempotencyTable",
	dynamodb.WithSortKeyAttr("sort_key"),
	dynamodb.WithStaticPKValue("idempotency#MyLambdaFunction"))
if err != nil {
	panic(err)
}
process := idempotency.Idempotent(func(_ context.Context, userID int) (map[string]any, error) {
	return map[string]any{"user_id": userID, "message": "success"}, nil
}, store, idempotency.WithKeyPrefix("orders"))

ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
if _, err := process(ctx, 12391); err != nil {
	panic(err)
}
item := table.onlyItem()
for _, name := range []string{"id", "sort_key", "status", "data"} {
	if value, ok := item[name].(*types.AttributeValueMemberS); ok {
		fmt.Printf("%s: %s\n", name, value.Value)
	}
}
fmt.Println(slices.Sorted(maps.Keys(item)))
Output:
id: idempotency#MyLambdaFunction
sort_key: orders#47c00b40b245c5389dfc3100ebb4b7a0
status: COMPLETED
data: {"message":"success","user_id":12391}
[data expiration id in_progress_expiration sort_key status]

func WithStaticPKValue

func WithStaticPKValue(value string) Option

WithStaticPKValue sets the partition key of every record when the table has a sort key; it defaults to "idempotency#" and the Lambda function's name. Without a sort key it is ignored.

func WithStatusAttr

func WithStatusAttr(name string) Option

WithStatusAttr names the status attribute, "status" by default.

func WithValidationKeyAttr

func WithValidationKeyAttr(name string) Option

WithValidationKeyAttr names the attribute of the payload validation hash, "validation" by default.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store keeps idempotency records in a DynamoDB table. It is safe for concurrent use, and one Store serves any number of idempotent functions.

func FromConfig

func FromConfig(cfg aws.Config, table string, opts ...Option) (*Store, error)

FromConfig returns a Store on table with a DynamoDB client built from cfg.

Example
package main

import (
	"context"
	"fmt"

	"github.com/aws/aws-sdk-go-v2/aws"

	"github.com/alessandrolattao/powertools-lambda-go/idempotency"
	"github.com/alessandrolattao/powertools-lambda-go/idempotency/dynamodb"
)

func main() {
	// In a Lambda function: cfg, err := config.LoadDefaultConfig(ctx).
	cfg := aws.Config{Region: "eu-west-1"}

	store, err := dynamodb.FromConfig(cfg, "IdempotencyTable")
	if err != nil {
		panic(err)
	}
	handler := idempotency.Idempotent(func(_ context.Context, orderID string) (string, error) {
		return "processed " + orderID, nil
	}, store)
	fmt.Printf("%T\n", handler)
}
Output:
func(context.Context, string) (string, error)

func New

func New(client Client, table string, opts ...Option) (*Store, error)

New returns a Store on table, calling DynamoDB through client. It fails with idempotency.ErrInvalidConfig when the options cannot work.

func (*Store) DeleteRecord

func (s *Store) DeleteRecord(ctx context.Context, key string) error

DeleteRecord implements idempotency.Store with a DeleteItem.

func (*Store) GetRecord

func (s *Store) GetRecord(ctx context.Context, key string) (*idempotency.Record, error)

GetRecord implements idempotency.Store with a strongly consistent GetItem.

func (*Store) PutRecord

func (s *Store) PutRecord(ctx context.Context, record idempotency.Record, now time.Time) error

PutRecord implements idempotency.Store with a conditional PutItem. When the condition fails, DynamoDB returns the record that holds the key with the error, which saves a read.

func (*Store) UpdateRecord

func (s *Store) UpdateRecord(ctx context.Context, record idempotency.Record) error

UpdateRecord implements idempotency.Store with an UpdateItem that sets the response, the expiry, the status and the validation hash.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL