Documentation ¶
Overview ¶
Package dynamo implements a simple key-value abstraction to store algebraic data types with AWS services:
↣ AWS DynamoDB
↣ AWS S3
Inspiration ¶
The library encourages developers to use Golang struct to define domain models, write correct, maintainable code. Using the library, the application can achieve the ideal data model that would require a single request to DynamoDB and model one-to-one, one-to-many and even many-to-many relations. The library uses generic programming style to implement actual storage I/O, while expose external domain object as `[T dynamo.Thing]` with implicit conversion back and forth between a concrete struct(s).
Essentially, it implement a following generic key-value trait to access domain objects. The library AWS Go SDK under the hood
type KeyVal[T any] interface { Put(T) error Get(T) (*T, error) Remove(T) error Update(T): (*T, error) Match(T): Seq[T] }
Getting started ¶
Define an application domain model using product types, which are strongly expressed by struct in Go.
type Person struct { Org curie.IRI `dynamodbav:"prefix,omitempty"` ID curie.IRI `dynamodbav:"suffix,omitempty"` Name string `dynamodbav:"name,omitempty"` Age int `dynamodbav:"age,omitempty"` Address string `dynamodbav:"address,omitempty"` }
Make sure that defined type implements dynamo.Thing interface for identity
func (p Person) HashKey() curie.IRI { return p.Org } func (p Person) SortKey() curie.IRI { return p.ID }
Use DynamoDB attributes from AWS Go SDK to specify marshalling rules https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/dynamodbattribute.
Create an implicit I/O endpoint to Dynamo DB table
db := keyval.New[Person](dynamo.WithURI("ddb:///my-table"))
Creates a new entity, or replaces an old entity with a new value.
err := db.Put( Person{ Org: curie.IRI("test"), ID: curie.IRI("8980789222"), Name: "Verner Pleishner", Age: 64, Address: "Blumenstrasse 14, Berne, 3013", } )
Lookup the struct using Get. This function takes input structure as key and return a new copy upon the completion. The only requirement - ID has to be defined.
val, err := db.Get(Person{Org: curie.IRI("test"), ID: curie.IRI("8980789222")}) switch err.(type) { case nil: // success case dynamo.NotFound: // not found default: // other i/o error }
Remove the entity
err := db.Remove(Person{Org: curie.IRI("test"), ID: curie.IRI("8980789222")})
Apply a partial update using Update function. This function takes a partially defined structure, patches the instance at storage and returns remaining attributes.
person := Person{ Org: "test", ID: "8980789222" Address: "Viktoriastrasse 37, Berne, 3013", } val, err := db.Update(person) if err != nil { ... }
Use following DynamoDB schema:
const Schema = (): ddb.TableProps => ({ partitionKey: {type: ddb.AttributeType.STRING, name: 'prefix'}, sortKey: {type: ddb.AttributeType.STRING, name: 'suffix'}, tableName: 'my-table', readCapacity: 1, writeCapacity: 1, })
See README at https://github.com/fogfish/dynamo
Index ¶
- func Codec10[T Thing, A, B, C, D, E, F, G, H, I, J any](a, b, c, d, e, f, g, h, i, j string) (CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], CodecOf[T, E], ...)
- func Codec2[T Thing, A, B any](a, b string) (CodecOf[T, A], CodecOf[T, B])
- func Codec3[T Thing, A, B, C any](a, b, c string) (CodecOf[T, A], CodecOf[T, B], CodecOf[T, C])
- func Codec4[T Thing, A, B, C, D any](a, b, c, d string) (CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D])
- func Codec5[T Thing, A, B, C, D, E any](a, b, c, d, e string) (CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], CodecOf[T, E])
- func Codec6[T Thing, A, B, C, D, E, F any](a, b, c, d, e, f string) (CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], CodecOf[T, E], ...)
- func Codec7[T Thing, A, B, C, D, E, F, G any](a, b, c, d, e, f, g string) (CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], CodecOf[T, E], ...)
- func Codec8[T Thing, A, B, C, D, E, F, G, H any](a, b, c, d, e, f, g, h string) (CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], CodecOf[T, E], ...)
- func Codec9[T Thing, A, B, C, D, E, F, G, H, I any](a, b, c, d, e, f, g, h, i string) (CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], CodecOf[T, E], ...)
- func Decode(av types.AttributeValue, val interface{}, coder ...Coder) (err error)
- func Encode(val interface{}, coder ...Coder) (types.AttributeValue, error)
- func Schema10[T Thing, A, B, C, D, E, F, G, H, I, J any](a, b, c, d, e, f, g, h, i, j string) (TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], TypeOf[T, E], ...)
- func Schema2[T Thing, A, B any](a, b string) (TypeOf[T, A], TypeOf[T, B])
- func Schema3[T Thing, A, B, C any](a, b, c string) (TypeOf[T, A], TypeOf[T, B], TypeOf[T, C])
- func Schema4[T Thing, A, B, C, D any](a, b, c, d string) (TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D])
- func Schema5[T Thing, A, B, C, D, E any](a, b, c, d, e string) (TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], TypeOf[T, E])
- func Schema6[T Thing, A, B, C, D, E, F any](a, b, c, d, e, f string) (TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], TypeOf[T, E], ...)
- func Schema7[T Thing, A, B, C, D, E, F, G any](a, b, c, d, e, f, g string) (TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], TypeOf[T, E], ...)
- func Schema8[T Thing, A, B, C, D, E, F, G, H any](a, b, c, d, e, f, g, h string) (TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], TypeOf[T, E], ...)
- func Schema9[T Thing, A, B, C, D, E, F, G, H, I any](a, b, c, d, e, f, g, h, i string) (TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], TypeOf[T, E], ...)
- type CodecOf
- type Coder
- type Config
- type Constraint
- type KeyVal
- type KeyValGetter
- type KeyValGetterNoContext
- type KeyValNoContext
- type KeyValPattern
- type KeyValPatternNoContext
- type KeyValReader
- type KeyValReaderNoContext
- type KeyValWriter
- type KeyValWriterNoContext
- type Option
- type Seq
- type SeqConfig
- type SeqLazy
- type Thing
- type Things
- type TypeOf
- type URL
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Codec10 ¶ added in v1.5.0
func Codec10[T Thing, A, B, C, D, E, F, G, H, I, J any](a, b, c, d, e, f, g, h, i, j string) ( CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], CodecOf[T, E], CodecOf[T, F], CodecOf[T, G], CodecOf[T, H], CodecOf[T, I], CodecOf[T, J], )
Codec10 builds Codec for 10 attributes
func Codec4 ¶ added in v1.5.0
func Codec4[T Thing, A, B, C, D any](a, b, c, d string) ( CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], )
Codec4 builds Codec for 4 attributes
func Codec5 ¶ added in v1.5.0
func Codec5[T Thing, A, B, C, D, E any](a, b, c, d, e string) ( CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], CodecOf[T, E], )
Codec5 builds Codec for 5 attributes
func Codec6 ¶ added in v1.5.0
func Codec6[T Thing, A, B, C, D, E, F any](a, b, c, d, e, f string) ( CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], CodecOf[T, E], CodecOf[T, F], )
Codec6 builds Codec for 6 attributes
func Codec7 ¶ added in v1.5.0
func Codec7[T Thing, A, B, C, D, E, F, G any](a, b, c, d, e, f, g string) ( CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], CodecOf[T, E], CodecOf[T, F], CodecOf[T, G], )
Codec7 builds Codec for 7 attributes
func Codec8 ¶ added in v1.5.0
func Codec8[T Thing, A, B, C, D, E, F, G, H any](a, b, c, d, e, f, g, h string) ( CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], CodecOf[T, E], CodecOf[T, F], CodecOf[T, G], CodecOf[T, H], )
Codec8 builds Codec for 8 attributes
func Codec9 ¶ added in v1.5.0
func Codec9[T Thing, A, B, C, D, E, F, G, H, I any](a, b, c, d, e, f, g, h, i string) ( CodecOf[T, A], CodecOf[T, B], CodecOf[T, C], CodecOf[T, D], CodecOf[T, E], CodecOf[T, F], CodecOf[T, G], CodecOf[T, H], CodecOf[T, I], )
Codec9 builds Codec for 9 attributes
func Decode ¶ added in v1.2.0
func Decode(av types.AttributeValue, val interface{}, coder ...Coder) (err error)
Decode is a helper function to decode core domain types from Dynamo DB format. The helper ensures compact URI de-serialization from DynamoDB schema.
type MyType struct { ID MyComplexType Name MyComplexType } var ID, Name = dynamo.Codec2[MyType, MyDynamoType, MyDynamoType]("ID", "Name") func (x *MyType) UnmarshalDynamoDBAttributeValue(av types.AttributeValue) error { type tStruct *MyType return dynamo.Decode(av, tStruct(x), ID.Decode((*MyDynamoType)(&x.ID)), Name.Decode((*MyDynamoType)(&x.Name)), ) }
func Encode ¶ added in v1.2.0
func Encode(val interface{}, coder ...Coder) (types.AttributeValue, error)
Encode is a helper function to encode core domain types into struct. The helper ensures compact URI serialization into DynamoDB schema.
type MyType struct { ID MyComplexType Name MyComplexType } var ID, Name = dynamo.Codec2[MyType, MyDynamoType, MyDynamoType]("ID", "Name") func (x MyType) MarshalDynamoDBAttributeValue() (types.AttributeValue, error) { type tStruct MyType return dynamo.Encode(av, tStruct(x), ID.Encode(MyDynamoType(x.ID)), Name.Encode(MyDynamoType(x.Name)), ) }
func Schema10 ¶ added in v1.5.0
func Schema10[T Thing, A, B, C, D, E, F, G, H, I, J any](a, b, c, d, e, f, g, h, i, j string) ( TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], TypeOf[T, E], TypeOf[T, F], TypeOf[T, G], TypeOf[T, H], TypeOf[T, I], TypeOf[T, J], )
Schema10 builds Constrain builder for product type of arity 10
func Schema4 ¶ added in v1.5.0
func Schema4[T Thing, A, B, C, D any](a, b, c, d string) ( TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], )
Schema4 builds Constrain builder for product type of arity 4
func Schema5 ¶ added in v1.5.0
func Schema5[T Thing, A, B, C, D, E any](a, b, c, d, e string) ( TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], TypeOf[T, E], )
Schema5 builds Constrain builder for product type of arity 5
func Schema6 ¶ added in v1.5.0
func Schema6[T Thing, A, B, C, D, E, F any](a, b, c, d, e, f string) ( TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], TypeOf[T, E], TypeOf[T, F], )
Schema6 builds Constrain builder for product type of arity 6
func Schema7 ¶ added in v1.5.0
func Schema7[T Thing, A, B, C, D, E, F, G any](a, b, c, d, e, f, g string) ( TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], TypeOf[T, E], TypeOf[T, F], TypeOf[T, G], )
Schema7 builds Constrain builder for product type of arity 7
func Schema8 ¶ added in v1.5.0
func Schema8[T Thing, A, B, C, D, E, F, G, H any](a, b, c, d, e, f, g, h string) ( TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], TypeOf[T, E], TypeOf[T, F], TypeOf[T, G], TypeOf[T, H], )
Schema8 builds Constrain builder for product type of arity 8
func Schema9 ¶ added in v1.5.0
func Schema9[T Thing, A, B, C, D, E, F, G, H, I any](a, b, c, d, e, f, g, h, i string) ( TypeOf[T, A], TypeOf[T, B], TypeOf[T, C], TypeOf[T, D], TypeOf[T, E], TypeOf[T, F], TypeOf[T, G], TypeOf[T, H], TypeOf[T, I], )
Schema9 builds Constrain builder for product type of arity 9
Types ¶
type CodecOf ¶ added in v1.5.0
CodecOf for struct fields, the type implement Encode/Decode primitives. Codec helps to implement semi-automated encoding/decoding algebraic data type into the format compatible with storage.
Let's consider scenario were application uses complex types that skips implementation of marshal/unmarshal protocols. Here the type MyComplexType needs to be casted to MyDynamoType that knows how to marshal/unmarshal the type.
type MyType struct { ID MyComplexType Name MyComplexType } var ID, Name = dynamo.Codec2[MyType, MyDynamoType, MyDynamoType]("ID", "Name") func (t MyType) MarshalDynamoDBAttributeValue() (*dynamodb.AttributeValue, error) { type tStruct MyType return dynamo.Encode(tStruct(p), ID.Encode(MyDynamoType(t.ID)), Name.Encode(MyDynamoType(t.Name)), ) }
type Coder ¶ added in v1.4.0
type Coder func(map[string]types.AttributeValue) (map[string]types.AttributeValue, error)
Coder is a function, applies transformation of generic dynamodb AttributeValue
type Constraint ¶ added in v1.6.0
type Constraint[T Thing] interface{ TypeOf(T) }
Constraint is a function that applies conditional expression to storage request. Each storage implements own constrains protocols. The module here defines a few constrain protocol. The structure of the constrain is abstracted away from the client.
See internal/constrain package to see details about its implementation
type KeyVal ¶
type KeyVal[T Thing] interface { KeyValReader[T] KeyValWriter[T] }
KeyVal is a generic key-value trait to access domain objects.
type KeyValGetter ¶ added in v1.2.0
KeyValGetter defines read by key notation
type KeyValGetterNoContext ¶ added in v1.2.0
KeyValGetterNoContext defines read by key notation
type KeyValNoContext ¶ added in v1.2.0
type KeyValNoContext[T Thing] interface { KeyValReaderNoContext[T] KeyValWriterNoContext[T] }
KeyValNoContext is a generic key-value trait to access domain objects.
type KeyValPattern ¶
KeyValPattern defines simple pattern matching lookup I/O
type KeyValPatternNoContext ¶ added in v1.2.0
KeyValPatternNoContext defines simple pattern matching lookup I/O
type KeyValReader ¶ added in v1.2.0
type KeyValReader[T Thing] interface { KeyValGetter[T] KeyValPattern[T] }
KeyValReader a generic key-value trait to read domain objects
type KeyValReaderNoContext ¶ added in v1.2.0
type KeyValReaderNoContext[T Thing] interface { KeyValGetterNoContext[T] KeyValPatternNoContext[T] }
KeyValReaderNoContext a generic key-value trait to read domain objects
type KeyValWriter ¶ added in v1.2.0
type KeyValWriter[T Thing] interface { Put(context.Context, T, ...Constraint[T]) error Remove(context.Context, T, ...Constraint[T]) error Update(context.Context, T, ...Constraint[T]) (T, error) }
KeyValWriter defines a generic key-value writer
type KeyValWriterNoContext ¶ added in v1.2.0
type KeyValWriterNoContext[T Thing] interface { Put(T, ...Constraint[T]) error Remove(T, ...Constraint[T]) error Update(T, ...Constraint[T]) (T, error) }
KeyValWriterNoContext defines a generic key-value writer
type Option ¶ added in v1.6.0
Option type to configure the connection
func WithAwsConfig ¶ added in v1.6.0
WithSession defines AWS I/O Session to be used in the context
func WithPrefixes ¶ added in v1.6.0
WithPrefixes defines prefixes for CURIEs
type Seq ¶
type Seq[T Thing] interface { SeqLazy[T] SeqConfig[T] // Sequence transformer FMap(func(T) error) error }
Seq is an interface to transform collection of objects
db.Match(dynamo.NewID("users")).FMap(func(x *T) error { ... })
type SeqConfig ¶
type SeqConfig[T Thing] interface { // Limit sequence size to N elements (pagination) Limit(int) Seq[T] // Continue limited sequence from the cursor Continue(Thing) Seq[T] // Reverse order of sequence Reverse() Seq[T] }
SeqConfig configures optional sequence behavior
type SeqLazy ¶
type SeqLazy[T Thing] interface { // Head lifts first element of sequence Head() (T, error) // Tail evaluates tail of sequence Tail() bool // Error returns error of stream evaluation Error() error // Cursor is the global position in the sequence Cursor() Thing }
SeqLazy is an interface to iterate through collection of objects at storage
type Thing ¶
Thing is the most generic item type used by the library to abstract writable/readable items into storage services.
The interfaces declares anything that have a unique identifier. The unique identity is exposed by pair of string: HashKey and SortKey.
type TypeOf ¶
type TypeOf[T Thing, A any] interface { Eq(A) Constraint[T] Ne(A) Constraint[T] Lt(A) Constraint[T] Le(A) Constraint[T] Gt(A) Constraint[T] Ge(A) Constraint[T] Is(string) Constraint[T] Exists() Constraint[T] NotExists() Constraint[T] }
TypeOf declares type descriptor to express Storage I/O Constrains.
Let's consider a following example:
type Person struct { curie.ID Name string `dynamodbav:"anothername,omitempty"` }
How to define a condition expression on the field Name? Golang struct defines and refers the field by `Name` but DynamoDB stores it under the attribute `anothername`. Struct field dynamodbav tag specifies serialization rules. Golang does not support a typesafe approach to build a correspondence between `Name` ⟷ `anothername`. Developers have to utilize dynamodb attribute name(s) in conditional expression and Golang struct name in rest of the code. It becomes confusing and hard to maintain.
The types TypeOf and SchemaN are helpers to declare builders for conditional expressions. Just declare a global variables next to type definition and use them across the application.
var name = dynamo.Schema1[Person, string]("Name") name.Eq("Joe Doe") name.NotExists()