How to Use AWS DynamoDB with Go: A Practical Guide with Localstack

golang docker aws dynamodb localstack

Introduction

AWS DynamoDB is a highly scalable, fast, and flexible NoSQL database that is a great choice for applications that require low latency and high throughput. In this tutorial, we will show you how to use AWS DynamoDB with Go and localstack to test our code. This guide will provide you with practical examples and explanations that will make it easy for you to use DynamoDB in your Go applications.

Prerequisites

Before we start, ensure that you have the following installed and set up:

  • Go programming language
  • AWS CLI
  • Localstack

Understanding AWS DynamoDB with Go

Before we get started with the code, let’s first understand the basics of DynamoDB and how it works with Go. DynamoDB is a fully managed, serverless database that provides consistent, fast performance with seamless scalability. It is a key-value and document database that delivers single-digit millisecond performance at any scale.

Go is a simple and powerful open-source programming language that is designed for building fast, efficient, and scalable applications. It is a perfect language for building cloud-native applications, and its simplicity and performance make it a great choice for working with AWS services like DynamoDB.

Setting up Localstack

Localstack is a tool that enables you to emulate AWS services on your local machine. It is a great tool for testing your applications without incurring any costs. To set up Localstack, follow these steps:

  1. Install Docker on your machine if you haven’t already.
  2. Pull the Localstack image from Docker Hub using the command docker pull localstack/localstack.
  3. Run the Localstack container with the command docker run -p 4566:4566 localstack/localstack.

Creating a DynamoDB Table

In this section, we will create a DynamoDB table using the AWS SDK for Go. Ensure that you have set up your AWS credentials by running aws configure. For Localstack, use any 12 digit number (like 112233445566) as AWS Access Key ID and same 12 digit number as AWS Secret Access Key since Localstack ignores the secret anyways. Default region name will be us-east-1 and Default output format will be json.

package main

import (
    "fmt"
    "log"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/dynamodb"
)

type Employee struct {
    ID   int
    Name string
    Age  int
}

func main() {
    sess, err := session.NewSession(&aws.Config{
        Region:   aws.String("us-east-1"),
        Endpoint: aws.String("http://localhost:4566"),
    })
    if err != nil {
        log.Fatalf("failed to start new session: %v", err)
    }

    svc := dynamodb.New(sess)

    createTable(svc)
}

func createTable(svc *dynamodb.DynamoDB) {
    input := &dynamodb.CreateTableInput{
        AttributeDefinitions: []*dynamodb.AttributeDefinition{
            {AttributeName: aws.String("ID"), AttributeType: aws.String("N")},
        },
        KeySchema: []*dynamodb.KeySchemaElement{
            {AttributeName: aws.String("ID"), KeyType: aws.String("HASH")},
        },
        ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
            ReadCapacityUnits:  aws.Int64(5),
            WriteCapacityUnits: aws.Int64(5),
        },
        TableName: aws.String("Employee"),
    }

    if _, err := svc.CreateTable(input); err != nil {
        log.Fatalf("failed to create table: %v", err)
    }

    fmt.Println("Table created successfully")
}

/*

$ go run main.go
Table created successfully

*/

In this code snippet, we define a struct called Employee and use the dynamodbattribute package to marshal and unmarshal data between Go and DynamoDB. We then create a new session and configure it to use Localstack. We create a new dynamodb service and use the CreateTable method to create a new table called “Employee” with an ID attribute that is a number.

Inserting Data into the Table

Now that we have created a table, we can insert data into it.

package main

import (
    "fmt"
    "log"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/service/dynamodb"
    "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

func insert(svc *dynamodb.DynamoDB) {
    item := Employee{
        ID:   1,
        Name: "John Doe",
        Age:  30,
    }

    av, err := dynamodbattribute.MarshalMap(item)
    if err != nil {
        log.Fatalf("failed to marshal item: %v", err)
    }

    input := &dynamodb.PutItemInput{
        Item:      av,
        TableName: aws.String("Employee"),
    }

    if _, err := svc.PutItem(input); err != nil {
        log.Fatalf("failed to put item: %v", err)
    }

    fmt.Println("Item inserted successfully")
}

/*

$ go run main.go insert.go
Item inserted successfully

*/

In this code snippet, we create a new Employee struct and marshal it into a DynamoDB attribute value map. We then use the PutItem method to insert the item into the “Employee” table.

Querying Data from the Table

Now that we have inserted data into the table, we can query it using the Query method.

package main

import (
    "fmt"
    "log"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/service/dynamodb"
    "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

func query(svc *dynamodb.DynamoDB) {
    input := &dynamodb.QueryInput{
        ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
            ":val": {
                N: aws.String("1"),
            },
        },
        KeyConditionExpression: aws.String("ID = :val"),
        TableName:              aws.String("Employee"),
    }

    result, err := svc.Query(input)
    if err != nil {
        log.Fatalf("failed to query: %v", err)
        return
    }

    for _, item := range result.Items {
        emp := Employee{}

        err = dynamodbattribute.UnmarshalMap(item, &emp)
        if err != nil {
            log.Fatalf("failed to unmarshal item: %v", err)
            return
        }

        fmt.Printf("%+v\\n", emp)
    }
}

/*

$ go run main.go query.go
{ID:1 Name:John Doe Age:30}\n%

/*

In this code snippet, we use the Query method to query the “Employee” table for all items with an ID of 1. We then unmarshal the results into a Employee struct and print it to the console.

Updating Data in the Table

To update data in the table, we use the UpdateItem method.

package main

import (
    "fmt"
    "log"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/service/dynamodb"
)

func update(svc *dynamodb.DynamoDB) {
    input := &dynamodb.UpdateItemInput{
        ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
            ":n": {
                S: aws.String("Jane Doe"),
            },
        },
        TableName: aws.String("Employee"),
        Key: map[string]*dynamodb.AttributeValue{
            "ID": {
                N: aws.String("1"),
            },
        },
        UpdateExpression: aws.String("set #name = :n"),
        ExpressionAttributeNames: map[string]*string{
            "#name": aws.String("Name"),
        },
    }

    if _, err := svc.UpdateItem(input); err != nil {
        log.Fatalf("failed to update item: %v", err)
    }

    fmt.Println("Item updated successfully")
}

/*

$ go run main.go update.go
Item updated successfully

*/

In this code snippet, we use the UpdateItem method to update the name of the employee with ID 1. We use the ExpressionAttributeValues parameter to define the new name, and the UpdateExpression parameter to specify that we want to update the Name attribute. We also use the ExpressionAttributeNames parameter to specify that Name is a reserved keyword and should be escaped with a # character.

Deleting Data from the Table

To delete data from the table, we use the DeleteItem method.

package main

import (
    "fmt"
    "log"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/service/dynamodb"
)

func delete(svc *dynamodb.DynamoDB) {
    input := &dynamodb.DeleteItemInput{
        TableName: aws.String("Employee"),
        Key: map[string]*dynamodb.AttributeValue{
            "ID": {
                N: aws.String("1"),
            },
        },
    }

    if _, err := svc.DeleteItem(input); err != nil {
        log.Fatalf("failed to delete item: %v", err)
    }

    fmt.Println("Item deleted successfully")
}

/*

$ go run main.go delete.go
Item deleted successfully

*/

In this code snippet, we use the DeleteItem method to delete the employee with ID 1 from the “Employee” table.

Querying Data with Sort Keys

DynamoDB is a powerful NoSQL database that supports querying data using sort keys. Sort keys allow you to sort and filter your data based on a specific criterion.

package main

import (
    "fmt"
    "log"

    "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"
)

type Employee struct {
    ID      int
    Name    string
    Age     int
    City    string
    Country string
}

func main() {
    sess, err := session.NewSession(&aws.Config{
        Region:   aws.String("us-east-1"),
        Endpoint: aws.String("http://localhost:4566"),
    })
    if err != nil {
        log.Fatalf("failed to create new session: %v", err)
    }

    svc := dynamodb.New(sess)
    input := &dynamodb.CreateTableInput{
        AttributeDefinitions: []*dynamodb.AttributeDefinition{
            {AttributeName: aws.String("ID"), AttributeType: aws.String("N")},
            {AttributeName: aws.String("Name"), AttributeType: aws.String("S")},
            {AttributeName: aws.String("City"), AttributeType: aws.String("S")},
        },
        KeySchema: []*dynamodb.KeySchemaElement{
            {AttributeName: aws.String("ID"), KeyType: aws.String("HASH")},
            {AttributeName: aws.String("Name"), KeyType: aws.String("RANGE")},
        },
        GlobalSecondaryIndexes: []*dynamodb.GlobalSecondaryIndex{
            {
                IndexName: aws.String("CityIndex"),
                KeySchema: []*dynamodb.KeySchemaElement{
                    {AttributeName: aws.String("ID"), KeyType: aws.String("HASH")},
                    {AttributeName: aws.String("City"), KeyType: aws.String("RANGE")},
                },
                Projection: &dynamodb.Projection{
                    ProjectionType: aws.String("ALL"),
                },
                ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
                    ReadCapacityUnits:  aws.Int64(5),
                    WriteCapacityUnits: aws.Int64(5),
                },
            },
        },
        ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
            ReadCapacityUnits:  aws.Int64(5),
            WriteCapacityUnits: aws.Int64(5),
        },
        TableName: aws.String("Employee2"),
    }

    if _, err := svc.CreateTable(input); err != nil {
        log.Fatalf("failed to create table: %v", err)
    }

    fmt.Println("Table created successfully")

    item := Employee{
        ID:      1,
        Name:    "John Doe",
        Age:     30,
        City:    "New York",
        Country: "USA",
    }

    av, err := dynamodbattribute.MarshalMap(item)
    if err != nil {
        log.Fatalf("failed to marshal item: %v", err)
    }

    input2 := &dynamodb.PutItemInput{
        Item:      av,
        TableName: aws.String("Employee2"),
    }

    if _, err := svc.PutItem(input2); err != nil {
        log.Fatalf("failed to insert item: %v", err)
    }

    fmt.Println("Item inserted successfully")

    input3 := &dynamodb.QueryInput{
        ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
            ":id":  {N: aws.String("1")},
            ":val": {S: aws.String("New York")},
        },
        KeyConditionExpression: aws.String("ID = :id and begins_with(City, :val)"),
        TableName:              aws.String("Employee2"),
        IndexName:              aws.String("CityIndex"),
    }

    result, err := svc.Query(input3)
    if err != nil {
        log.Fatalf("failed to query table: %v", err)
    }

    for _, item := range result.Items {
        emp := Employee{}

        if err := dynamodbattribute.UnmarshalMap(item, &emp); err != nil {
            log.Fatalf("failed to unmarshal item: %v", err)
        }

        fmt.Printf("%+v\\n", emp)
    }
}

/*

$ go run sortkeys/main.go
Table created successfully
Item inserted successfully
{ID:1 Name:John Doe Age:30 City:New York Country:USA}\n%

*/

In this code snippet, we create a new DynamoDB table called “Employee2” and insert a new item into it. We then use the Query method with a begins_with function to query the data based on the City attribute. We also define a new index called “CityIndex” and use it to optimize the query. This example demonstrates how you can use sort keys to filter your data and optimize your queries.

Conclusion

In this tutorial, we have shown you how to use AWS DynamoDB with Go and Localstack to test your code. We have covered the basic database operations including creating a table, inserting data into it, querying data from it, updating data, and deleting data. We have also demonstrated how to use sort keys to filter your data and optimize your queries. With this knowledge, you can now integrate DynamoDB into your Go applications and build scalable, high-performance applications that can handle any workload. Happy coding!

In case you would like to get notified about more articles like this, please subscribe to my blog. The full source code is available in the Github repository https://github.com/abvarun226/blog-source-code/tree/master/how-to-use-aws-dynamodb-with-go-a-practical-guide-with-localstack

Get new posts by email

Comments

comments powered by Disqus