CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/golang-github-com--aws--aws-sdk-go-v2

AWS SDK for Go v2 with 130+ service clients, Request/Send pattern, and context support.

Overview
Eval results
Files

real-world-scenarios.mddocs/examples/

Real-World Scenarios and Edge Cases

S3: Complete Upload with All Options

import (
    "context"
    "crypto/md5"
    "encoding/base64"
    "fmt"
    "io"
    "os"
    "time"
    
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/aws/awserr"
    "github.com/aws/aws-sdk-go-v2/aws/external"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

func uploadWithAllOptions(filename, bucket, key string) error {
    cfg, err := external.LoadDefaultAWSConfig()
    if err != nil {
        return fmt.Errorf("config error: %w", err)
    }
    
    svc := s3.New(cfg)
    
    file, err := os.Open(filename)
    if err != nil {
        return fmt.Errorf("file error: %w", err)
    }
    defer file.Close()
    
    // Calculate MD5 for integrity check
    hash := md5.New()
    io.Copy(hash, file)
    md5sum := base64.StdEncoding.EncodeToString(hash.Sum(nil))
    file.Seek(0, 0) // Reset file pointer
    
    // Get file info
    stat, _ := file.Stat()
    
    // Set timeout
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
    defer cancel()
    
    req := svc.PutObjectRequest(&s3.PutObjectInput{
        Bucket:        aws.String(bucket),
        Key:           aws.String(key),
        Body:          file,
        ContentLength: aws.Int64(stat.Size()),
        ContentType:   aws.String("application/octet-stream"),
        ContentMD5:    aws.String(md5sum),
        
        // Metadata (custom headers)
        Metadata: map[string]string{
            "uploaded-by": "my-app",
            "version":     "1.0",
        },
        
        // Storage class
        StorageClass: s3.StorageClassStandardIa,
        
        // Server-side encryption
        ServerSideEncryption: s3.ServerSideEncryptionAes256,
        
        // Tags
        Tagging: aws.String("Environment=prod&Team=engineering"),
        
        // ACL
        ACL: s3.ObjectCannedACLPrivate,
    })
    
    req.SetContext(ctx)
    
    _, err = req.Send()
    if err != nil {
        if aerr, ok := err.(awserr.Error); ok {
            switch aerr.Code() {
            case s3.ErrCodeNoSuchBucket:
                return fmt.Errorf("bucket %s does not exist", bucket)
            case "AccessDenied":
                return fmt.Errorf("access denied: check IAM permissions")
            case "EntityTooLarge":
                return fmt.Errorf("file too large (max 5GB for single upload)")
            default:
                return fmt.Errorf("upload failed [%s]: %s", aerr.Code(), aerr.Message())
            }
        }
        return err
    }
    
    return nil
}

S3: Multipart Upload with Progress

import (
    "github.com/aws/aws-sdk-go-v2/service/s3/s3manager"
)

func uploadLargeFile(filename, bucket, key string) error {
    cfg, _ := external.LoadDefaultAWSConfig()
    
    // Create uploader with custom options
    uploader := s3manager.NewUploader(cfg, func(u *s3manager.Uploader) {
        u.PartSize = 10 * 1024 * 1024 // 10MB parts (min 5MB)
        u.Concurrency = 5              // 5 concurrent uploads
        u.LeavePartsOnError = false    // Cleanup failed parts
        u.MaxUploadParts = 10000       // Max parts (AWS limit)
    })
    
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    result, err := uploader.Upload(&s3manager.UploadInput{
        Bucket: aws.String(bucket),
        Key:    aws.String(key),
        Body:   file,
        
        // Optional: Add metadata, encryption, etc.
        Metadata: map[string]string{
            "filename": filepath.Base(filename),
        },
    })
    
    if err != nil {
        if aerr, ok := err.(awserr.Error); ok {
            if aerr.Code() == s3.ErrCodeNoSuchUpload {
                return fmt.Errorf("multipart upload was aborted")
            }
        }
        return err
    }
    
    fmt.Printf("Uploaded to: %s (ETag: %s)\n", result.Location, *result.ETag)
    return nil
}

S3: Concurrent Download

func downloadLargeFile(bucket, key, filename string) error {
    cfg, _ := external.LoadDefaultAWSConfig()
    
    downloader := s3manager.NewDownloader(cfg, func(d *s3manager.Downloader) {
        d.PartSize = 10 * 1024 * 1024 // 10MB parts
        d.Concurrency = 5              // 5 concurrent downloads
    })
    
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    numBytes, err := downloader.Download(file, &s3.GetObjectInput{
        Bucket: aws.String(bucket),
        Key:    aws.String(key),
    })
    if err != nil {
        return err
    }
    
    fmt.Printf("Downloaded %d bytes\n", numBytes)
    return nil
}

S3: Presigned URL Generation

func generatePresignedURL(bucket, key string, expiry time.Duration) (string, error) {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := s3.New(cfg)
    
    // GET presigned URL
    req := svc.GetObjectRequest(&s3.GetObjectInput{
        Bucket: aws.String(bucket),
        Key:    aws.String(key),
    })
    
    url, err := req.Presign(expiry)
    if err != nil {
        return "", err
    }
    
    return url, nil
}

// PUT presigned URL (for uploads)
func generateUploadURL(bucket, key string, expiry time.Duration) (string, error) {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := s3.New(cfg)
    
    req := svc.PutObjectRequest(&s3.PutObjectInput{
        Bucket: aws.String(bucket),
        Key:    aws.String(key),
    })
    
    return req.Presign(expiry)
}

S3: List Objects with Pagination

func listAllObjects(bucket string) ([]string, error) {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := s3.New(cfg)
    
    var keys []string
    
    req := svc.ListObjectsV2Request(&s3.ListObjectsV2Input{
        Bucket: aws.String(bucket),
        // Optional: Prefix, Delimiter, MaxKeys
    })
    
    p := req.Paginate()
    for p.Next() {
        page := p.CurrentPage().(*s3.ListObjectsV2Output)
        for _, obj := range page.Contents {
            keys = append(keys, *obj.Key)
        }
    }
    
    if err := p.Err(); err != nil {
        return nil, err
    }
    
    return keys, nil
}

DynamoDB: Query with Pagination

import "github.com/aws/aws-sdk-go-v2/service/dynamodb"

func queryUsersByStatus(tableName, status string) ([]map[string]dynamodb.AttributeValue, error) {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := dynamodb.New(cfg)
    
    var items []map[string]dynamodb.AttributeValue
    
    req := svc.QueryRequest(&dynamodb.QueryInput{
        TableName:              aws.String(tableName),
        IndexName:              aws.String("status-index"), // GSI
        KeyConditionExpression: aws.String("status = :status"),
        ExpressionAttributeValues: map[string]dynamodb.AttributeValue{
            ":status": {S: aws.String(status)},
        },
        // Optional: FilterExpression, ProjectionExpression, ScanIndexForward
    })
    
    p := req.Paginate()
    for p.Next() {
        page := p.CurrentPage().(*dynamodb.QueryOutput)
        items = append(items, page.Items...)
    }
    
    if err := p.Err(); err != nil {
        return nil, err
    }
    
    return items, nil
}

DynamoDB: Conditional Update

func conditionalUpdate(tableName, id, newValue string, expectedVersion int) error {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := dynamodb.New(cfg)
    
    req := svc.UpdateItemRequest(&dynamodb.UpdateItemInput{
        TableName: aws.String(tableName),
        Key: map[string]dynamodb.AttributeValue{
            "id": {S: aws.String(id)},
        },
        UpdateExpression: aws.String("SET #v = :newval, version = version + :inc"),
        ConditionExpression: aws.String("version = :expected"),
        ExpressionAttributeNames: map[string]string{
            "#v": "value", // Use placeholder for reserved words
        },
        ExpressionAttributeValues: map[string]dynamodb.AttributeValue{
            ":newval":   {S: aws.String(newValue)},
            ":expected": {N: aws.String(fmt.Sprintf("%d", expectedVersion))},
            ":inc":      {N: aws.String("1")},
        },
        ReturnValues: dynamodb.ReturnValueAllNew,
    })
    
    resp, err := req.Send()
    if err != nil {
        if aerr, ok := err.(awserr.Error); ok {
            if aerr.Code() == "ConditionalCheckFailedException" {
                return fmt.Errorf("version mismatch - item was modified concurrently")
            }
        }
        return err
    }
    
    // resp.Attributes contains the updated item
    return nil
}

DynamoDB: Batch Operations

func batchGetItems(tableName string, ids []string) ([]map[string]dynamodb.AttributeValue, error) {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := dynamodb.New(cfg)
    
    // DynamoDB batch limit is 100 items
    batchSize := 100
    var allItems []map[string]dynamodb.AttributeValue
    
    for i := 0; i < len(ids); i += batchSize {
        end := i + batchSize
        if end > len(ids) {
            end = len(ids)
        }
        
        batch := ids[i:end]
        keys := make([]map[string]dynamodb.AttributeValue, len(batch))
        for j, id := range batch {
            keys[j] = map[string]dynamodb.AttributeValue{
                "id": {S: aws.String(id)},
            }
        }
        
        req := svc.BatchGetItemRequest(&dynamodb.BatchGetItemInput{
            RequestItems: map[string]dynamodb.KeysAndAttributes{
                tableName: {Keys: keys},
            },
        })
        
        resp, err := req.Send()
        if err != nil {
            return nil, err
        }
        
        allItems = append(allItems, resp.Responses[tableName]...)
        
        // Handle unprocessed keys (throttling)
        if len(resp.UnprocessedKeys) > 0 {
            time.Sleep(100 * time.Millisecond)
            // Retry unprocessed keys...
        }
    }
    
    return allItems, nil
}

Lambda: Invoke with Payload

import (
    "encoding/json"
    "github.com/aws/aws-sdk-go-v2/service/lambda"
)

type LambdaPayload struct {
    Action string `json:"action"`
    Data   map[string]interface{} `json:"data"`
}

type LambdaResponse struct {
    StatusCode int               `json:"statusCode"`
    Body       string            `json:"body"`
    Headers    map[string]string `json:"headers"`
}

func invokeLambdaSync(functionName string, payload LambdaPayload) (*LambdaResponse, error) {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := lambda.New(cfg)
    
    payloadBytes, _ := json.Marshal(payload)
    
    req := svc.InvokeRequest(&lambda.InvokeInput{
        FunctionName:   aws.String(functionName),
        Payload:        payloadBytes,
        InvocationType: lambda.InvocationTypeRequestResponse, // Sync
        LogType:        lambda.LogTypeTail,                    // Get logs in response
    })
    
    resp, err := req.Send()
    if err != nil {
        return nil, err
    }
    
    // Check function error
    if resp.FunctionError != nil {
        return nil, fmt.Errorf("lambda error: %s", *resp.FunctionError)
    }
    
    // Parse response
    var result LambdaResponse
    if err := json.Unmarshal(resp.Payload, &result); err != nil {
        return nil, fmt.Errorf("response parse error: %w", err)
    }
    
    // Decode logs if present
    if resp.LogResult != nil {
        logBytes, _ := base64.StdEncoding.DecodeString(*resp.LogResult)
        fmt.Printf("Lambda logs:\n%s\n", string(logBytes))
    }
    
    return &result, nil
}

// Async invocation
func invokeLambdaAsync(functionName string, payload LambdaPayload) error {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := lambda.New(cfg)
    
    payloadBytes, _ := json.Marshal(payload)
    
    req := svc.InvokeRequest(&lambda.InvokeInput{
        FunctionName:   aws.String(functionName),
        Payload:        payloadBytes,
        InvocationType: lambda.InvocationTypeEvent, // Async - returns immediately
    })
    
    _, err := req.Send()
    return err
}

EC2: Advanced Instance Management

import "github.com/aws/aws-sdk-go-v2/service/ec2"

func listInstancesFiltered(filters map[string][]string) ([]*ec2.Instance, error) {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := ec2.New(cfg)
    
    // Build filter list
    ec2Filters := []ec2.Filter{}
    for name, values := range filters {
        ec2Filters = append(ec2Filters, ec2.Filter{
            Name:   aws.String(name),
            Values: values,
        })
    }
    
    req := svc.DescribeInstancesRequest(&ec2.DescribeInstancesInput{
        Filters: ec2Filters,
    })
    
    var instances []*ec2.Instance
    
    p := req.Paginate()
    for p.Next() {
        page := p.CurrentPage().(*ec2.DescribeInstancesOutput)
        for _, reservation := range page.Reservations {
            instances = append(instances, reservation.Instances...)
        }
    }
    
    if err := p.Err(); err != nil {
        return nil, err
    }
    
    return instances, nil
}

// Example usage:
// instances, _ := listInstancesFiltered(map[string][]string{
//     "instance-state-name": {"running"},
//     "tag:Environment":     {"production"},
//     "instance-type":       {"t3.micro", "t3.small"},
// })

SQS: Message Processing with Visibility Timeout

import (
    "fmt"
    "time"
    "github.com/aws/aws-sdk-go-v2/service/sqs"
)

func processMessages(queueURL string, handler func(sqs.Message) error) error {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := sqs.New(cfg)
    
    for {
        // Receive messages
        req := svc.ReceiveMessageRequest(&sqs.ReceiveMessageInput{
            QueueUrl:            aws.String(queueURL),
            MaxNumberOfMessages: aws.Int64(10),        // Max 10
            WaitTimeSeconds:     aws.Int64(20),        // Long polling (0-20)
            VisibilityTimeout:   aws.Int64(30),        // 30 seconds to process
            MessageAttributeNames: []string{"All"},
            AttributeNames: []sqs.QueueAttributeName{
                sqs.QueueAttributeNameAll,
            },
        })
        
        resp, err := req.Send()
        if err != nil {
            return err
        }
        
        if len(resp.Messages) == 0 {
            continue // No messages, keep polling
        }
        
        // Process each message
        for _, msg := range resp.Messages {
            // Process message
            if err := handler(msg); err != nil {
                fmt.Printf("Failed to process message %s: %v\n", *msg.MessageId, err)
                
                // Make visible again for retry
                chgReq := svc.ChangeMessageVisibilityRequest(&sqs.ChangeMessageVisibilityInput{
                    QueueUrl:          aws.String(queueURL),
                    ReceiptHandle:     msg.ReceiptHandle,
                    VisibilityTimeout: aws.Int64(0), // Immediately visible
                })
                chgReq.Send()
                continue
            }
            
            // Delete successfully processed message
            delReq := svc.DeleteMessageRequest(&sqs.DeleteMessageInput{
                QueueUrl:      aws.String(queueURL),
                ReceiptHandle: msg.ReceiptHandle,
            })
            
            if _, err := delReq.Send(); err != nil {
                fmt.Printf("Failed to delete message: %v\n", err)
            }
        }
    }
}

SNS: Publish with Message Attributes

import "github.com/aws/aws-sdk-go-v2/service/sns"

func publishWithAttributes(topicARN, message string, attributes map[string]string) error {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := sns.New(cfg)
    
    // Build message attributes
    msgAttrs := make(map[string]sns.MessageAttributeValue)
    for key, val := range attributes {
        msgAttrs[key] = sns.MessageAttributeValue{
            DataType:    aws.String("String"),
            StringValue: aws.String(val),
        }
    }
    
    // Add numeric attribute
    msgAttrs["timestamp"] = sns.MessageAttributeValue{
        DataType:    aws.String("Number"),
        StringValue: aws.String(fmt.Sprintf("%d", time.Now().Unix())),
    }
    
    req := svc.PublishRequest(&sns.PublishInput{
        TopicArn:          aws.String(topicARN),
        Message:           aws.String(message),
        MessageAttributes: msgAttrs,
        // Optional: Subject, MessageStructure (for mobile push)
    })
    
    resp, err := req.Send()
    if err != nil {
        return err
    }
    
    fmt.Printf("Published: MessageID=%s\n", *resp.MessageId)
    return nil
}

CloudWatch Logs: Stream Reading

import "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"

func tailLogs(logGroup, logStream string) error {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := cloudwatchlogs.New(cfg)
    
    var nextToken *string
    
    for {
        req := svc.GetLogEventsRequest(&cloudwatchlogs.GetLogEventsInput{
            LogGroupName:  aws.String(logGroup),
            LogStreamName: aws.String(logStream),
            NextToken:     nextToken,
            StartFromHead: aws.Bool(true),
            Limit:         aws.Int64(100),
        })
        
        resp, err := req.Send()
        if err != nil {
            return err
        }
        
        // Print events
        for _, event := range resp.Events {
            ts := time.Unix(*event.Timestamp/1000, 0)
            fmt.Printf("[%s] %s\n", ts.Format(time.RFC3339), *event.Message)
        }
        
        // Check if done
        if resp.NextForwardToken == nil || 
           (nextToken != nil && *resp.NextForwardToken == *nextToken) {
            break // No more events
        }
        
        nextToken = resp.NextForwardToken
        time.Sleep(1 * time.Second) // Rate limiting
    }
    
    return nil
}

Credentials: Assume Role

import "github.com/aws/aws-sdk-go-v2/service/sts"

func assumeRole(roleARN, sessionName string) (aws.Config, error) {
    cfg, _ := external.LoadDefaultAWSConfig()
    stsSvc := sts.New(cfg)
    
    req := stsSvc.AssumeRoleRequest(&sts.AssumeRoleInput{
        RoleArn:         aws.String(roleARN),
        RoleSessionName: aws.String(sessionName),
        DurationSeconds: aws.Int64(3600), // 1 hour
        // Optional: ExternalId, Policy, Tags
    })
    
    resp, err := req.Send()
    if err != nil {
        return cfg, err
    }
    
    // Create new config with assumed role credentials
    assumedConfig := cfg.Copy()
    assumedConfig.Credentials = aws.NewStaticCredentialsProvider(
        *resp.Credentials.AccessKeyId,
        *resp.Credentials.SecretAccessKey,
        *resp.Credentials.SessionToken,
    )
    
    return assumedConfig, nil
}

Edge Cases and Gotchas

Nil Pointer Handling

// WRONG - panics if field is nil
name := *resp.User.Name

// CORRECT - check before dereferencing
if resp.User != nil && resp.User.Name != nil {
    name := *resp.User.Name
}

// BEST - use helper (returns empty string if nil)
name := aws.StringValue(resp.User.Name)

Empty vs Nil Slices

// Nil slice - field omitted from request
Tags: nil

// Empty slice - field sent as empty array []
Tags: []ec2.Tag{}

// For required fields, use empty slice if no values:
Filters: []ec2.Filter{} // Better than nil for required fields

Type Assertions from map[string]interface{}

// Arguments come as interface{} - must type assert
id := p.Args["id"].(string)          // Panics if wrong type
limit := p.Args["limit"].(int)        // Panics if wrong type

// Safe assertions:
id, ok := p.Args["id"].(string)
if !ok {
    return nil, fmt.Errorf("id must be string")
}

// Handle optional arguments:
limit := 10 // default
if val, ok := p.Args["limit"]; ok {
    if limitInt, ok := val.(int); ok {
        limit = limitInt
    }
}

Context Cancellation

ctx, cancel := context.WithCancel(context.Background())

// Cancel from another goroutine
go func() {
    time.Sleep(5 * time.Second)
    cancel()
}()

req := svc.GetObjectRequest(input)
req.SetContext(ctx)

_, err := req.Send()
if err == context.Canceled {
    // Request was cancelled
}
if err == context.DeadlineExceeded {
    // Request timed out
}

Pagination Edge Cases

// Empty results
p := req.Paginate()
hasResults := false
for p.Next() {
    hasResults = true
    // Process page
}

if !hasResults {
    if err := p.Err(); err != nil {
        // Error occurred
        return err
    }
    // No results (not an error)
}

// Always check p.Err() after loop
for p.Next() {
    // Process
}
if err := p.Err(); err != nil {
    // Pagination failed mid-stream
    return err
}

Concurrent Requests (Thread Safety)

import "sync"

// Requests are NOT thread-safe - create per-goroutine
func concurrentUploads(bucket string, files []string) error {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := s3.New(cfg) // Service client IS thread-safe
    
    var wg sync.WaitGroup
    errChan := make(chan error, len(files))
    
    for _, file := range files {
        wg.Add(1)
        go func(f string) {
            defer wg.Done()
            
            // Create NEW request per goroutine
            req := svc.PutObjectRequest(&s3.PutObjectInput{
                Bucket: aws.String(bucket),
                Key:    aws.String(f),
                // ... upload file
            })
            
            if _, err := req.Send(); err != nil {
                errChan <- err
            }
        }(file)
    }
    
    wg.Wait()
    close(errChan)
    
    // Check for errors
    for err := range errChan {
        return err
    }
    
    return nil
}

Custom Configuration

Custom HTTP Client

import "net/http"

cfg, _ := external.LoadDefaultAWSConfig()

cfg.HTTPClient = &http.Client{
    Timeout: 30 * time.Second,
    Transport: &http.Transport{
        MaxIdleConns:        100,
        MaxIdleConnsPerHost: 10,
        MaxConnsPerHost:     10,
        IdleConnTimeout:     90 * time.Second,
        TLSHandshakeTimeout: 10 * time.Second,
    },
}

Custom Endpoint (LocalStack, MinIO)

cfg.EndpointResolver = aws.EndpointResolverFunc(
    func(service, region string) (aws.Endpoint, error) {
        if service == "s3" {
            return aws.Endpoint{
                URL:           "http://localhost:4566", // LocalStack
                SigningRegion: region,
                Source:        aws.EndpointSourceCustom,
            }, nil
        }
        if service == "dynamodb" {
            return aws.Endpoint{
                URL:           "http://localhost:8000", // DynamoDB Local
                SigningRegion: region,
            }, nil
        }
        // Use default for other services
        return aws.Endpoint{}, fmt.Errorf("unknown endpoint")
    },
)

Retry Configuration

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

cfg.Retryer = retry.NewStandard(func(s *retry.Standard) {
    s.MaxAttempts = 5
    s.MaxBackoff = 30 * time.Second
})

Logging

cfg.LogLevel = aws.LogDebugWithHTTPBody // Verbose logging
cfg.Logger = aws.NewDefaultLogger()

// Custom logger
cfg.Logger = aws.LoggerFunc(func(classification ...interface{}) {
    fmt.Println(classification...)
})

Testing Patterns

Mocking with Interfaces

// Define interface for testing
type S3API interface {
    GetObjectRequest(*s3.GetObjectInput) s3.GetObjectRequest
    PutObjectRequest(*s3.PutObjectInput) s3.PutObjectRequest
}

// Function accepts interface
func downloadFile(svc S3API, bucket, key string) error {
    req := svc.GetObjectRequest(&s3.GetObjectInput{
        Bucket: aws.String(bucket),
        Key:    aws.String(key),
    })
    resp, err := req.Send()
    // ...
}

// In tests, implement interface with mock

LocalStack Integration

func newLocalStackConfig() aws.Config {
    cfg := aws.NewConfig()
    
    cfg.Region = "us-east-1"
    cfg.Credentials = aws.NewStaticCredentialsProvider("test", "test", "")
    
    cfg.EndpointResolver = aws.EndpointResolverFunc(
        func(service, region string) (aws.Endpoint, error) {
            return aws.Endpoint{
                URL:           "http://localhost:4566",
                SigningRegion: region,
            }, nil
        },
    )
    
    // Disable SSL verification for local testing
    cfg.HTTPClient = &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
        },
    }
    
    return *cfg
}

Performance Patterns

Connection Reuse

// Reuse service clients - they manage connection pooling
var s3Client *s3.S3

func init() {
    cfg, _ := external.LoadDefaultAWSConfig()
    s3Client = s3.New(cfg)
}

// Use s3Client in multiple functions

Batch API Calls

// Use batch operations when available
// Instead of:
for _, id := range ids {
    svc.DeleteObjectRequest(&s3.DeleteObjectInput{...}).Send()
}

// Do this:
objects := make([]s3.ObjectIdentifier, len(ids))
for i, id := range ids {
    objects[i] = s3.ObjectIdentifier{Key: aws.String(id)}
}
svc.DeleteObjectsRequest(&s3.DeleteObjectsInput{
    Bucket: aws.String(bucket),
    Delete: &s3.Delete{Objects: objects},
}).Send()

Request Caching (for reads)

var cache = make(map[string]*s3.GetObjectOutput)
var cacheMu sync.RWMutex

func getCached(bucket, key string) (*s3.GetObjectOutput, error) {
    cacheKey := bucket + "/" + key
    
    cacheMu.RLock()
    if cached, ok := cache[cacheKey]; ok {
        cacheMu.RUnlock()
        return cached, nil
    }
    cacheMu.RUnlock()
    
    // Fetch from S3
    resp, err := svc.GetObjectRequest(&s3.GetObjectInput{
        Bucket: aws.String(bucket),
        Key:    aws.String(key),
    }).Send()
    
    if err != nil {
        return nil, err
    }
    
    cacheMu.Lock()
    cache[cacheKey] = resp
    cacheMu.Unlock()
    
    return resp, nil
}

Error Recovery Patterns

Exponential Backoff for Throttling

func uploadWithBackoff(bucket, key string, body io.Reader) error {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := s3.New(cfg)
    
    maxRetries := 5
    baseDelay := 100 * time.Millisecond
    
    for attempt := 0; attempt < maxRetries; attempt++ {
        req := svc.PutObjectRequest(&s3.PutObjectInput{
            Bucket: aws.String(bucket),
            Key:    aws.String(key),
            Body:   body,
        })
        
        _, err := req.Send()
        if err == nil {
            return nil // Success
        }
        
        if aerr, ok := err.(awserr.Error); ok {
            if aerr.Code() == "SlowDown" || aerr.Code() == "ServiceUnavailable" {
                // Exponential backoff
                delay := baseDelay * time.Duration(1<<uint(attempt))
                time.Sleep(delay)
                continue
            }
        }
        
        return err // Non-retryable error
    }
    
    return fmt.Errorf("max retries exceeded")
}

Checking Object Existence

func objectExists(bucket, key string) (bool, error) {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := s3.New(cfg)
    
    req := svc.HeadObjectRequest(&s3.HeadObjectInput{
        Bucket: aws.String(bucket),
        Key:    aws.String(key),
    })
    
    _, err := req.Send()
    if err != nil {
        if aerr, ok := err.(awserr.Error); ok {
            if aerr.Code() == s3.ErrCodeNoSuchKey || aerr.Code() == "NotFound" {
                return false, nil // Object doesn't exist (not an error)
            }
        }
        return false, err // Real error
    }
    
    return true, nil // Object exists
}

Handling Large Responses

// For large list operations, always paginate
func listAllObjects(bucket string) ([]string, error) {
    cfg, _ := external.LoadDefaultAWSConfig()
    svc := s3.New(cfg)
    
    var keys []string
    
    req := svc.ListObjectsV2Request(&s3.ListObjectsV2Input{
        Bucket:  aws.String(bucket),
        MaxKeys: aws.Int64(1000), // Max per page
    })
    
    p := req.Paginate()
    for p.Next() {
        page := p.CurrentPage().(*s3.ListObjectsV2Output)
        for _, obj := range page.Contents {
            keys = append(keys, *obj.Key)
        }
        
        // Optional: Add delay to avoid throttling
        if page.IsTruncated != nil && *page.IsTruncated {
            time.Sleep(100 * time.Millisecond)
        }
    }
    
    return keys, p.Err()
}

DynamoDB Attribute Conversion

// String attribute
attr := dynamodb.AttributeValue{S: aws.String("value")}

// Number attribute (stored as string)
attr := dynamodb.AttributeValue{N: aws.String("123")}
attr := dynamodb.AttributeValue{N: aws.String("123.45")}

// Boolean attribute
attr := dynamodb.AttributeValue{BOOL: aws.Bool(true)}

// Binary attribute
attr := dynamodb.AttributeValue{B: []byte("binary data")}

// Null attribute
attr := dynamodb.AttributeValue{NULL: aws.Bool(true)}

// List attribute
attr := dynamodb.AttributeValue{
    L: []dynamodb.AttributeValue{
        {S: aws.String("item1")},
        {S: aws.String("item2")},
    },
}

// Map attribute
attr := dynamodb.AttributeValue{
    M: map[string]dynamodb.AttributeValue{
        "nested": {S: aws.String("value")},
        "count":  {N: aws.String("10")},
    },
}

// String set
attr := dynamodb.AttributeValue{SS: []string{"a", "b", "c"}}

// Number set
attr := dynamodb.AttributeValue{NS: []string{"1", "2", "3"}}

Request Modification

// Modify request before sending
req := svc.GetObjectRequest(input)

// Add custom headers
req.HTTPRequest.Header.Set("X-Custom-Header", "value")

// Modify using handlers
req.Handlers.Send.PushFront(func(r *aws.Request) {
    // Called before HTTP request sent
    fmt.Printf("Sending request to: %s\n", r.HTTPRequest.URL)
})

req.Handlers.Complete.PushBack(func(r *aws.Request) {
    // Called after request completes
    fmt.Printf("Request completed with status: %d\n", r.HTTPResponse.StatusCode)
})

_, err := req.Send()

For more patterns and complete API reference, see reference documentation.

Install with Tessl CLI

npx tessl i tessl/golang-github-com--aws--aws-sdk-go-v2

docs

examples

real-world-scenarios.md

index.md

tile.json