/dynago

Simplify working with AWS DynamoDB.

Primary LanguageGoMIT LicenseMIT

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.