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 ¶
- Constants
- type Client
- type Option
- func WithDataAttr(name string) Option
- func WithExpiryAttr(name string) Option
- func WithInProgressExpiryAttr(name string) Option
- func WithKeyAttr(name string) Option
- func WithSortKeyAttr(name string) Option
- func WithStaticPKValue(value string) Option
- func WithStatusAttr(name string) Option
- func WithValidationKeyAttr(name string) Option
- type Store
- func (s *Store) DeleteRecord(ctx context.Context, key string) error
- func (s *Store) GetRecord(ctx context.Context, key string) (*idempotency.Record, error)
- func (s *Store) PutRecord(ctx context.Context, record idempotency.Record, now time.Time) error
- func (s *Store) UpdateRecord(ctx context.Context, record idempotency.Record) error
Examples ¶
Constants ¶
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 ¶
WithDataAttr names the attribute of the stored response, "data" by default.
func WithExpiryAttr ¶
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 ¶
WithInProgressExpiryAttr names the attribute of an in-progress record's expiry, in epoch milliseconds; it defaults to "in_progress_expiration".
func WithKeyAttr ¶
WithKeyAttr names the partition key attribute, "id" by default.
func WithSortKeyAttr ¶
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 ¶
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 ¶
WithStatusAttr names the status attribute, "status" by default.
func WithValidationKeyAttr ¶
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 ¶
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 ¶
New returns a Store on table, calling DynamoDB through client. It fails with idempotency.ErrInvalidConfig when the options cannot work.
func (*Store) DeleteRecord ¶
DeleteRecord implements idempotency.Store with a DeleteItem.
func (*Store) GetRecord ¶
GetRecord implements idempotency.Store with a strongly consistent GetItem.
func (*Store) PutRecord ¶
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 ¶
UpdateRecord implements idempotency.Store with an UpdateItem that sets the response, the expiry, the status and the validation hash.