Categories
AWS Beginner DynamoDB Go Intermediate tutorial

Testing locally DynamoDB in Go

Testing locally DynamoDB in Go
Testing locally DynamoDB in Go

Testing DynamoDB locally in Go or any language is tricky. In this quick article I will provide a solution on how to test locally using a docker container and your web service that interfaces with AWS DynamoDB.

First, you will need a DynamoDB instance running locally so that you can interact with it from your service.

DynamoDB Instance with Docker-Compose

dynamodb:
    image:  "amazon/dynamodb-local:latest"
    command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data"
    container_name: dynamodb
    ports:
      - 8000:8000
    environment:
      AWS_REGION: "us-east-1"
      AWS_ACCESS_KEY_ID: "YOUR_AKID"
      AWS_SECRET_ACCESS_KEY: "YOUR_SECRET_KEY"
      AWS_SESSION_TOKEN: "TOKEN"
    working_dir: "/home/dynamodblocal"
    volumes:
      -  "./docker/dynamodb:/home/dynamodblocal/data"

The code above shows you how to create a simple DynamoDB instance for you to test with locally user a docker-compose.yml file. In this article we are assuming that you are running your webservice that has visibility to the DynamoDB container, the setup of the web service is out of scope for this article.

When using AWS SDK v2 for Golang things are done way differently then on version v1. In v1, you could provide an endpoint, a region and credentials that required for you to test locally. Just for reference, this is how you test in aws-sdk-go-v1:

        creds := credentials.NewEnvCredentials()
	_, err := creds.Get()
	if err != nil {
		return nil, err
	}
	sess, err := session.NewSession(&aws.Config{
		Region:      aws.String("us-east-1"),
		Endpoint:    aws.String("http://localhost:8000"),
		Credentials: creds},
	)
	if err != nil {
		return nil, err
	}
	// Create DynamoDB client
	svc := dynamodb.New(sess)

The endpoint value is whatever url you specified in your docker-compose file for you AWS DynamoDB instance.

Implement AWS SDK Go v2

Now to answer the question of this topic: Testing locally DynamoDB, how can we accomplish this? Compared to v1, we have to do things differently. In v2 you have to have a several awsconfig.LoadOptionFunc’s. You will need the WithRegion, WithEndpointResolver, and lastly for local testing WithCredentialsProvider. For v2, make sure you have an IAM Role that is able to talk to your ec2 instance or whatever infrastructure you are using to serve your app and the DynamoDB instance.

customResolver := aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
	if os.Getenv("APP_ENV") == "local" {
		return aws.Endpoint{
			URL: config.Dynamo.Endpoint,
		}, nil
	}
	// returning EndpointNotFoundError will allow the service to fallback to it's default resolution
	return aws.Endpoint{}, &aws.EndpointNotFoundError{}
})
var (
	cfg aws.Config
	err error
)
if os.Getenv("APP_ENV") == "local" {
	cfg, err = awsconfig.LoadDefaultConfig(context.TODO(), awsconfig.WithRegion("us-east-1"), awsconfig.WithEndpointResolver(customResolver), awsconfig.WithCredentialsProvider(credentials.StaticCredentialsProvider{
		Value: aws.Credentials{
			AccessKeyID: "dummy", SecretAccessKey: "dummy", SessionToken: "dummy",
		Source: "Hard-coded credentials; values are irrelevant for local DynamoDB",
		},
	}))
} else {
	cfg, err = awsconfig.LoadDefaultConfig(context.TODO(), awsconfig.WithRegion("us-east-1"), awsconfig.WithEndpointResolver(customResolver))
}	
if err != nil {
	panic(err)
}
svc := dynamodb.NewFromConfig(cfg)

In the code above, you will notice that I check for an environment variable to equal “local”. The reason for this is, in v2 you are discouraged from using AWS access keys. When you test locally, the container requires an AWS access key, therefore you can create dummy credentials to use when testing locally.

Thank you for taking the time to read this information. You can find other articles here.

Categories
Beginner Go tutorial

Sentinel Errors, should we use them in Go?

Sentinel Errors, should we use them in Go?

Sentinel errors, should we use them in Go? When you use a sentinel error in your code, you are providing a way for your consumer to fail gracefully. How can you accomplish this? Let us first understand how to raise an error in Go.


func main() {
  // First way, using errors package
  err := errors.New("You have raise an error using the errors package")
  // Second way, using the fmt package
  uid := "randomUIDValue091"
  err = fmt.Errorf("%s is invalid format.", uid)
}

Currently, you can create an error using two methods. The first as shown in the sample code, you can use the errors package, which in turn enables you to use the New operator to generate an error. The second example shown in the sample code allows you to create an error using the fmt package calling on the Errorf method, which in turn returns an error type.

Sentinel Errors

Now that we understand how to create an error in Go, what is a sentinel error? Some errors are meant to signal that the code can no longer move forward due to the current state. Dave Cheney, a developer who has been active in Go for many years used this term to describe these errors:

The name descends from the practice in computer programming of using a specific value to signify that no further process is possible. So to [sic] with Go, we use specific values to signify an error.

Dave Cheney

Here are some existing examples of a Sentinel Errors are: io.EOF, or constans in the syscall lib syscall.ENOENT.

You typically declare the sentinel error at the package level. You also start the name with Err; io.EOF is a notable exception. Treat these errors as read-only (In go we cannot enforce this, but it would be an error if you as the programmer were to change these). Just to be clear then, when we use sentinel errors this is meant to signal that you cannot start or continue processing.

package main

import (
	"fmt"
	"io"
	"os"
)

const FILE = "/tmp/GeeksforGeeks.txt"

func main() {
	// Create tmp file that is empty
	cFile, err := os.Create(FILE)
	defer cFile.Close()

	// Open the file for read
	rFile, err := os.Open(FILE)
	defer rFile.Close()

	// Read the first 5 bytes
	data := make([]byte, 5)
	_, err = rFile.Read(data)

	if err == io.EOF {
		fmt.Println("You have come to the end of the file")
	}
}

The code above does couple simple tasks, it creates a file, it then opens the file and lastly it reads the 5 first bytes of the file. If the content is empty, which in this case it is, it returns the io.EOF error, meaning there is no more content. In my code, I compare err with the package level sentinel error io.EOF and if true, gracefully deal with it. Follow the link to play around with the above code.

Constant Sentinel error in your code; good idea?

package custerr

// Declare Sentinel type
type Sentinel string

// Implement Error interface 
func(s Sentinel) Error() string {
  return string(s)
}

In the code above, I have declared a type Sentinel, and then implemented the Error interface.

package mypkg

const (
  ErrBz = custerr.Sentinel("bz error")
  ErrBr = custerr.Sentinel("br error")
)

In the code above we are casting a string to type Sentinel; it could be a bit confusing considering it looks like we are calling a function, but that is not the case. The problem with this is, the code is not idiomatic. Using sentinel errors create package coupling which in turn could lead to potential cyclic import errors.

Conclusion

Do not use sentinel errors, we can use other ways to express errors and to check for specific types of errors. Stay tuned, we will address this in a future article! Don’t miss out on other tutorials.

Categories
Beginner Go tutorial

Go Pointers, scary or not?

Go Pointers, scary or not?

Go Pointers, scary or not? If you are new to Go, and you are confused as to what an * is in front of a variable or the & in front of a variable are? You have come to the right place. Let us run through this oddity for those that have not programmed in C or C++.

What is the Point…er in Go?

A pointer is, a variable that points to the location in memory of where a value is stored.

var f int32 = 2
var b bool = false
What a variable looks like in memory
How a variable looks in memory

In programming, every variable is stored in a memory address. Different types of variables can take up different amounts of memory. In the provided example, we have two variable, one of type int32 which takes 4 bytes and the other Boolean which only takes 1. Variable f starts at address location 1 and variable b starts a address 5.

Variable declaration that shows location of normal value and it's pointer.
How a pointer looks like in memory

A pointer can reference the location of variable that is of any type. A pointer also has a fixed size regardless of the size of the typed being pointed at. Pointers enable a lot of cool things when used correctly. Let us go in depth.

The & is the address operator. This value is used in front of the variable and it returns the address of the memory location.

The * is the indirection operator. This value goes in front of the variable that is of pointer type and returns the pointed-to value.

foo := 2
pointerFoo := &foo
sum := 2 + *pointerFoo
fmt.Println(sum) // prints out 7

Click here and play around on the Go playground.

How to use?

The downside of dereferencing is if you do this on a non-nil pointer, the system will panic and you will have to recover from this state.

The pointer then becomes its own type of pointer type. It is prefixed with a * before a type name. A pointer type can be based on any valid type. On a side note, when using the new operator this in turn will create a pointer variable. The new pointer variable will return a zero value instance of the provided type.

var i = new(int) // returns 0
var s = new(string) // returns ""
var b = new(boolean) // returns false
etc...

The new function is rarely used. For structs, use an & before a struct literal to create a pointer instance. You can’t do this on primitive literals such as numbers, Booleans and strings. The reason for this is that they don’t have a memory address; they exit only at compile time.

foo := &Foo{}
var s string
stringPointer := &s

I hope you have a better understanding of what a Go pointer is, and you don’t find it scary or intimidating when coding in Go and you stumble upon this. Go Pointers, scary or not? We can assert they are not scary and provide great flexibility in our program. For more tutorials, just follow the link