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 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 Config
- type Constraint
- type DynamoDB
- type KeyVal
- type KeyValGetter
- type KeyValPattern
- type KeyValReader
- type KeyValWriter
- type Option
- type S3
- 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 Schema10 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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
Types ¶
type Constraint ¶
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 DynamoDB ¶
type DynamoDB interface { GetItem(context.Context, *dynamodb.GetItemInput, ...func(*dynamodb.Options)) (*dynamodb.GetItemOutput, error) PutItem(context.Context, *dynamodb.PutItemInput, ...func(*dynamodb.Options)) (*dynamodb.PutItemOutput, error) DeleteItem(context.Context, *dynamodb.DeleteItemInput, ...func(*dynamodb.Options)) (*dynamodb.DeleteItemOutput, error) UpdateItem(context.Context, *dynamodb.UpdateItemInput, ...func(*dynamodb.Options)) (*dynamodb.UpdateItemOutput, error) Query(context.Context, *dynamodb.QueryInput, ...func(*dynamodb.Options)) (*dynamodb.QueryOutput, error) }
DynamoDB declares interface of original AWS DynamoDB API used by the library
type KeyVal ¶
type KeyVal[T Thing] interface { KeyValReader[T] KeyValWriter[T] }
KeyVal is a generic key-value trait to access domain objects.
type KeyValGetter ¶
KeyValGetter defines read by key notation
type KeyValPattern ¶
KeyValPattern defines simple pattern matching lookup I/O
type KeyValReader ¶
type KeyValReader[T Thing] interface { KeyValGetter[T] KeyValPattern[T] }
KeyValReader a generic key-value trait to read domain objects
type KeyValWriter ¶
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 Option ¶ added in v2.1.0
type Option func(cfg *Config)
Option type to configure the connection
func WithPrefixes ¶ added in v2.1.0
WithPrefixes defines prefixes for CURIEs
func WithService ¶ added in v2.1.0
Configure AWS Service for the client
type S3 ¶
type S3 interface { GetObject(context.Context, *s3.GetObjectInput, ...func(*s3.Options)) (*s3.GetObjectOutput, error) PutObject(context.Context, *s3.PutObjectInput, ...func(*s3.Options)) (*s3.PutObjectOutput, error) DeleteObject(context.Context, *s3.DeleteObjectInput, ...func(*s3.Options)) (*s3.DeleteObjectOutput, error) ListObjectsV2(context.Context, *s3.ListObjectsV2Input, ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) }
S3 declares AWS API used by the library
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()