Home

Awesome

Dynago

codecov

The aim of this package is to make it easier to work with AWS DynamoDB.

Documentation

For full documentation see pkg.go.dev.

Usage

Basic

package main

import (
	"fmt"
	"os"
	"time"

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

type Schema struct {}

func (s *Schema) PrimaryKeys() []string {
	return []string{"PK", "SK"}
}

type Post struct {
	// Embed a struct that implements the dynago.Keyer interface.
	*Schema

	// Set attribute name with `attr` tag if it needs to be different
	// than field name. Use `fmt:"Post#{}"` to indicate how the value
	// will be stored in DynamoDB.
	ID string `attr:"PK" fmt:"Post#{}"`

	Created  time.Time `attr:"SK" fmt:"Created#{}"`
	AuthorID string
	Title    string
	Body     string
}

func main() {
	// Get client.
	ddb := dynago.New(getDynamoDB(), &dynago.Config{
		DefaultTableName: "tmp",
	})

	// Put item in DynamoDB.
	p := Post{
		ID:      "hello-world",
		Title:   "Hi",
		Body:    "Hello world!",
		Created: time.Now(),
	}
	if err := ddb.PutItem(&p).Exec(); err != nil {
		panic(err)
	}

	// Get same item from DynamoDB. Fields used in the primary key
	// must be set.
	p2 := Post{
		ID:      p.ID,
		Created: p.Created,
	}
	if err := ddb.GetItem(&p2).Exec(); err != nil {
		panic(err)
	}
	fmt.Println(p2)
}

func getDynamoDB() *dynamodb.DynamoDB {
	os.Setenv("AWS_SDK_LOAD_CONFIG", "true")
	sess, err := session.NewSession()
	if err != nil {
		panic(err)
	}
	return dynamodb.New(sess)
}

Additional Attributes

package main

import (
	"fmt"
	"os"
	"reflect"
	"time"

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

type Schema struct {}

func (s *Schema) PrimaryKeys() []string {
	return []string{"PK", "SK"}
}

type Post struct {
	// Embed a struct that implements the dynago.Keyer interface.
	*Schema

	ID       string `attr:"PK" fmt:"Post#{}"`
	AuthorID string
	Title    string
	Body     string
	Created  time.Time `attr:"SK" fmt:"Created#{}"`
}

type Author struct {
	ID   string `attr:"PK" fmt:"Author#{}"`

	// Copy same value to attribute AltName by using `copy:"AltName"` in tag.
	Name string `copy:"AltName"`
}

func main() {
	// Get client.
	ddb := dynago.New(getDynamoDB(), &dynago.Config{
		DefaultTableName: "tmp",
		AdditionalAttrs:  additionalAttrs,
	})

	// ...
}

func additionalAttrs(item map[string]*dynamodb.AttributeValue, v reflect.Value) {
	ty := v.Type().Name()

	// Add a "Type" attribute to every item
	item["Type"] = &dynamodb.AttributeValue{S: &ty}

	// Add additional attributes for specific types
	switch val := v.Interface().(type) {
	case Author:
		// Add a fat partition or sparse global secondary index to
		// make querying for all authors possible
		author := fmt.Sprintf("Author#%s", val.ID)
		item["GSIPK"] = &dynamodb.AttributeValue{S: &ty}
		item["GSISK"] = &dynamodb.AttributeValue{S: &author}
	}
}

Compound Field Attributes

type Event struct {
	Org string `attr:"PK" fmt:"Org#{}"`

	// In this `fmt` tag, {} is equivalent to {Country}. You can
	// reference a different field name by putting it's name in
	// curly brackets.
	Country string `attr:"SK" fmt:"Country#{}#City#{City}"`

	// Since the City is specified in the "SK" attribute, we can
	// skip putting it in another attribute if we want.
	City    string `attr:"-"`
	Created time.Time
}

Contribute

Make a pull request.