{"id":13412220,"url":"https://github.com/fogfish/dynamo","last_synced_at":"2025-06-22T03:37:54.917Z","repository":{"id":37036751,"uuid":"229493737","full_name":"fogfish/dynamo","owner":"fogfish","description":"Generic Golang Key/Value trait for AWS storage services","archived":false,"fork":false,"pushed_at":"2024-02-19T20:04:44.000Z","size":449,"stargazers_count":18,"open_issues_count":5,"forks_count":5,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-18T16:25:56.778Z","etag":null,"topics":["aws-dynamodb","aws-s3","dynamodb","golang","key-value","orm","orm-library","type-safe"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fogfish.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-12-21T23:08:08.000Z","updated_at":"2024-07-13T17:53:44.000Z","dependencies_parsed_at":"2023-02-10T18:15:38.450Z","dependency_job_id":"595a9484-4926-444e-96f1-11272c7779f4","html_url":"https://github.com/fogfish/dynamo","commit_stats":{"total_commits":199,"total_committers":1,"mean_commits":199.0,"dds":0.0,"last_synced_commit":"073df6f229efca0caf608ab10050ba41f30f09e1"},"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fogfish%2Fdynamo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fogfish%2Fdynamo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fogfish%2Fdynamo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fogfish%2Fdynamo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fogfish","download_url":"https://codeload.github.com/fogfish/dynamo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244350800,"owners_count":20439286,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws-dynamodb","aws-s3","dynamodb","golang","key-value","orm","orm-library","type-safe"],"created_at":"2024-07-30T20:01:22.264Z","updated_at":"2025-03-19T03:30:45.056Z","avatar_url":"https://github.com/fogfish.png","language":"Go","readme":"# dynamo\n\nThe library implements a simple key-value abstraction to store algebraic, linked-data data types at AWS storage services: AWS DynamoDB and AWS S3.\n\n[![Version](https://img.shields.io/github/v/tag/fogfish/dynamo?label=version)](https://github.com/fogfish/dynamo/releases)\n[![Documentation](https://pkg.go.dev/badge/github.com/fogfish/dynamo/v3)](https://pkg.go.dev/github.com/fogfish/dynamo/v3)\n[![Build Status](https://github.com/fogfish/dynamo/workflows/build/badge.svg)](https://github.com/fogfish/dynamo/actions/)\n[![Git Hub](https://img.shields.io/github/last-commit/fogfish/dynamo.svg)](https://github.com/fogfish/dynamo)\n[![Coverage Status](https://coveralls.io/repos/github/fogfish/dynamo/badge.svg?branch=main)](https://coveralls.io/github/fogfish/dynamo?branch=main)\n[![Go Report Card](https://goreportcard.com/badge/github.com/fogfish/dynamo/v3)](https://goreportcard.com/report/github.com/fogfish/dynamo/v3)\n[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go)\n\n\n## Inspiration\n\nThe library encourages developers to use Golang struct to define domain models, write correct, maintainable code. Using this 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). The library uses [AWS Golang SDK v2](https://github.com/aws/aws-sdk-go-v2) under the hood.\n\nEssentially, the library implement a following generic key-value trait to access domain objects. \n\n```go\ntype KeyVal[T dynamo.Thing] interface {\n  Put(T) error\n  Get(T) (T, error)\n  Remove(T) (T, error)\n  Update(T) (T, error)\n  Match(T) ([]T, error)\n}\n```\n\nThe library philosophy and use-cases are covered in depth at the post\n[How To Model Any Relational Data in DynamoDB With dynamo library](example/relational/README.md) or continue reading the Getting started section.\n\n\n## Getting started\n\nThe library requires Go **1.18** or later due to usage of [generics](https://go.dev/blog/intro-generics).\n\nThe latest version of the library is available at its `main` branch. All development, including new features and bug fixes, take place on the `main` branch using forking and pull requests as described in contribution guidelines. The stable version is available via Golang modules.\n\n\n- [dynamo](#dynamo)\n  - [Inspiration](#inspiration)\n  - [Getting started](#getting-started)\n    - [Data types definition](#data-types-definition)\n    - [DynamoDB I/O](#dynamodb-io)\n    - [Error Handling](#error-handling)\n    - [Hierarchical structures](#hierarchical-structures)\n    - [Sequences and Pagination](#sequences-and-pagination)\n    - [Linked data](#linked-data)\n    - [Type projections](#type-projections)\n    - [Custom codecs for core domain types](#custom-codecs-for-core-domain-types)\n    - [DynamoDB Expressions](#dynamodb-expressions)\n      - [Projection Expression](#projection-expression)\n      - [Conditional Expression](#conditional-expression)\n      - [Update Expression](#update-expression)\n    - [Optimistic Locking](#optimistic-locking)\n    - [Batch I/O](#batch-io)\n    - [Configure DynamoDB](#configure-dynamodb)\n    - [AWS S3 Support](#aws-s3-support)\n  - [How To Contribute](#how-to-contribute)\n    - [commit message](#commit-message)\n    - [bugs](#bugs)\n  - [License](#license)\n\n\n### Data types definition\n\nData types definition is an essential part of development with `dynamo` library. Golang structs declares domain of your application. Public fields are serialized into DynamoDB attributes, the field tag `dynamodbav` controls marshal/unmarshal processes.\n\nThe library demands from each structure implementation of `Thing` interface. This type acts as struct annotation -- Golang compiler raises an error at compile time if other data type is supplied to the dynamo library. Secondly, each structure defines unique \"composite primary key\". The library encourages definition of both partition and sort keys using a special data type `curie.IRI`. This type is a synonym to compact Internationalized Resource Identifiers, which facilitates linked-data, hierarchical structures and cheap relations between data items. `curie.IRI` is a synonym to the built-in `string` type so that anything castable to string suite to model the keys as alternative solution.   \n\n```go\ntype Person struct {\n  Org     curie.IRI `dynamodbav:\"prefix,omitempty\"`\n  ID      curie.IRI `dynamodbav:\"suffix,omitempty\"`\n  Name    string    `dynamodbav:\"name,omitempty\"`\n  Age     int       `dynamodbav:\"age,omitempty\"`\n  Address string    `dynamodbav:\"address,omitempty\"`\n}\n\n//\n// Identity implements thing interface\nfunc (p Person) HashKey() curie.IRI { return p.Org }\nfunc (p Person) SortKey() curie.IRI { return p.ID }\n\n//\n// this data type is a normal Golang struct\n// just create an instance, fill required fields\nvar person := Person{\n  Org:     curie.IRI(\"University:Kiel\"),\n  ID:      curie.IRI(\"Professor:8980789222\"),\n  Name:    \"Verner Pleishner\",\n  Age:     64,\n  Address: \"Blumenstrasse 14, Berne, 3013\",\n}\n```\n\nThis is it! Your application is ready to read/write data to/form DynamoDB tables.\n\n\n### DynamoDB I/O\n\nPlease [see and try examples](examples). Its cover all basic use-cases with runnable code snippets, check the post [How To Model Any Relational Data in DynamoDB With dynamo library](examples/relational/README.md) for deep-dive into library philosophy.\n\n```bash\ngo run examples/keyval/main.go ddb:///my-table\n```\n\nThe following code snippet shows a typical I/O patterns\n\n```go\nimport (\n  \"github.com/fogfish/dynamo/v2/service/ddb\"\n)\n\n//\n// Create dynamodb client and bind it with the table.\n// The client is type-safe and support I/O with a single type (e.g. Person).\n// Use options to specify params.\ndb, err := ddb.New[Person](ddb.WithTable(\"my-table\"))\n\n//\n// Write the struct with Put\nif err := db.Put(context.TODO(), person); err != nil {\n}\n\n//\n// Lookup the struct using Get. This function takes input structure as key\n// and return a new copy upon the completion. The only requirement - ID has to\n// be defined.\nval, err := db.Get(context.TODO(),\n  Person{\n    Org: curie.IRI(\"University:Kiel\"),\n    ID:  curie.IRI(\"Professor:8980789222\"),\n  },\n)\n\nswitch {\ncase nil:\n  // success\ncase recoverNotFound(err):\n  // not found\ndefault:\n  // other i/o error\n}\n\n//\n// Apply a partial update using Update function. This function takes \n// a partially defined structure, patches the instance at storage and \n// returns remaining attributes.\nval, err := db.Update(context.TODO(),\n  Person{\n    Org:     curie.IRI(\"University:Kiel\"),\n    ID:      curie.IRI(\"Professor:8980789222\"),\n    Address: \"Viktoriastrasse 37, Berne, 3013\",\n  }\n)\n\nif err != nil { /* ... */ }\n\n//\n// Remove the struct using Remove give partially defined struct with ID\n_, err := db.Remove(context.TODO(),\n  Person{\n    Org: curie.IRI(\"University:Kiel\"),\n    ID:  curie.IRI(\"Professor:8980789222\"),\n  }\n)\n\nif err != nil { /* ... */ }\n```\n\n### Error Handling\n\nThe library enforces for \"assert errors for behavior, not type\" as the error handling strategy, see [the post](https://tech.fog.fish/2022/07/05/assert-golang-errors-for-behavior.html) for details. \n\nUse following behaviors to recover from errors:\n\n```go\ntype ErrorCode interface{ ErrorCode() string }\n\ntype NotFound interface { NotFound() string }\n\ntype PreConditionFailed interface { PreConditionFailed() bool }\n\ntype Conflict interface { Conflict() bool }\n\ntype Gone interface { Gone() bool }\n```\n\n\n### Hierarchical structures\n\nThe library support definition of `A ⟼ B` relation for data elements. Let's consider message threads as a classical examples for such hierarchies:\n\n```\nA\n├ B\n├ C\n│ ├ D  \n│ └ E\n│   └ F\n└ G\n```\n\nComposite sort key is core concept to organize hierarchies. It facilitates linked-data, hierarchical structures and cheap relations between data items. An application declares node path using composite sort key design pattern. For example, the root is `thread:A`, 2nd rank node `⟨thread:A, B⟩`, 3rd rank node `⟨thread:A, C/D⟩` and so on `⟨thread:A, C/E/F⟩`. Each `id` declares partition and sub nodes. The library implement a `Match` function, supply the node identity and it returns sequence of child elements.\n\n```go\n//\n// Match uses partition key to match DynamoDB entries.\n// It returns a sequence of matched data elements.  \ndb.Match(context.TODO(), Message{Thread: \"thread:A\"})\n\n//\n// Match uses partition key and partial sort key to match DynamoDB entries. \ndb.Match(context.TODO(), Message{Thread: \"thread:A\", ID: \"C\"})\n```\n\nSee [advanced example](examples/relational/) for details on managing linked-data. \n\n\n### Sequences and Pagination\n\nHierarchical structures is the way to organize collections, lists, sets, etc. The `Match` returns a lazy [Sequence](https://pkg.go.dev/github.com/fogfish/dynamo?readme=expanded#Seq) that represents your entire collection. Sometimes, your need to split the collection into sequence of pages.\n\n```go\n// 1. Set the limit on the stream \nseq, cursor, err := db.Match(context.TODO(),\n  Message{Thread: \"thread:A\", ID: \"C\"},\n  dynamo.Limit(25),\n)\n\n// 2. Continue I/O with a new stream, supply the cursor\nseq := db.Match(context.TODO(),\n  Message{Thread: \"thread:A\", ID: \"C\"},\n  dynamo.Limit(25),\n  cursor,\n)\n```\n\n\n### Linked data\n\nCross-linking of structured data is an essential part of type safe domain driven design. The library helps developers to model relations between data instances using familiar data type.\n\n```go\ntype Person struct {\n  Org     curie.IRI  `dynamodbav:\"prefix,omitempty\"`\n  ID      curie.IRI  `dynamodbav:\"suffix,omitempty\"`\n  Leader  *curie.IRI `dynamodbav:\"leader,omitempty\"`\n}\n```\n\n`ID` and `Leader` are sibling, equivalent data types. `ID` is only used as primary identity, `Leader` is a \"pointer\" to linked-data. The library advices usage of compact Internationalized Resource Identifiers (`curie.IRI`) for this purpose. Semantic Web publishes structured data using this type so that it can be interlinked by applications.\n\n\n### Type projections\n\nOften, there is an established system of the types in the application. It is not convenient to inject dependencies to the `dynamo` library. Also, the usage of secondary indexes requires multiple projections of core type.  \n\n```go\n// \n// original core type\ntype Person struct {\n  Org     string `dynamodbav:\"prefix,omitempty\"`\n  ID      string `dynamodbav:\"suffix,omitempty\"`\n  Name    string `dynamodbav:\"name,omitempty\"`\n  Age     int    `dynamodbav:\"age,omitempty\"`\n  Country string `dynamodbav:\"country,omitempty\"`\n}\n\n//\n// the core type projection that uses ⟨Org, ID⟩ as composite key\n// e.g. this projection supports writes to DynamoDB table\ntype dbPerson Person\n\nfunc (p dbPerson) HashKey() curie.IRI { return curie.IRI(p.Org) }\nfunc (p dbPerson) SortKey() curie.IRI { return curie.IRI(p.ID) }\n\n//\n// the core type projection that uses ⟨Org, Name⟩ as composite key\n// e.g. the projection support lookup of employer\ntype dbNamedPerson Person\n\nfunc (p dbNamedPerson) HashKey() curie.IRI { return curie.IRI(p.Org) }\nfunc (p dbNamedPerson) SortKey() curie.IRI { return curie.IRI(p.Name) }\n\n//\n// the core type projection that uses ⟨Country, Name⟩ as composite key\ntype dbCitizen Person\n\nfunc (p dbCitizen) HashKey() curie.IRI { return curie.IRI(p.Country) }\nfunc (p dbCitizen) SortKey() curie.IRI { return curie.IRI(p.Name) }\n```\n\n### Custom codecs for core domain types\n\nDevelopment of complex Golang application might lead developers towards [Standard Package Layout](https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1). It becomes extremely difficult to isolate dependencies from core data types to this library and AWS SDK. The library support serialization of core type to dynamo using custom codecs \n\n```go\n/*** core.go ***/\n\n// 1. complex domain type is defined\ntype ID struct {/* ... */}\n\n// 2. structure with core types is defined, no deps to dynamo library\ntype Person struct {\n  Org      ID  `dynamodbav:\"prefix,omitempty\"`\n  ID       ID  `dynamodbav:\"suffix,omitempty\"`\n}\n\n/*** aws/ddb/ddb.go ***/\n\nimport (\n  \"github.com/fogfish/dynamo/service/ddb\"\n)\n\n// 3. declare codecs for complex core domain type \ntype id core.ID\n\nfunc (id) MarshalDynamoDBAttributeValue() (types.AttributeValue, error) {\n  /* ...*/\n}\n\nfunc (*id) UnmarshalDynamoDBAttributeValue(types.AttributeValue) error {\n  /* ...*/\n}\n\n// aws/ddb/ddb.go\n// 2. type alias to core type implements dynamo custom codec\ntype dbPerson Person\n\n// 3. custom codec for structure field is defined \nvar (\n  codecHashKey = ddb.Codec[dbPerson, id](\"Org\")\n  codecSortKey = ddb.Codec[dbPerson, id](\"ID\")\n)\n\n// 4. use custom codec\nfunc (p dbPerson) MarshalDynamoDBAttributeValue() (types.AttributeValue, error) {\n  type tStruct dbPerson\n  return ddb.Encode(av, tStruct(p),\n    codecHashKey.Encode(id(p.Org)),\n    codecSortKey.Encode(id(p.ID))),\n  )\n}\n\nfunc (x *dbPerson) UnmarshalDynamoDBAttributeValue(av types.AttributeValue) error {\n  type tStruct *dbPerson\n  return ddb.Decode(av, tStruct(x),\n    codecHashKey.Decode((*id)(\u0026x.Org)),\n    codecSortKey.Decode((*id)(\u0026x.ID))),\n  )\n}\n```\n\n### DynamoDB Expressions\n\nIn Amazon DynamoDB, there is [a concept of expressions](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html) to denote the attributes to read; indicate various conditions while doing I/O.\n\n\n#### Projection Expression\n\n[Projection expression](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html) defines attributes to be read from the table. The library automatically defines projection expression for each request. The expression is derived from the datatype definition. \n\n```go\n// The type projects: prefix, suffix \u0026 name attributes.\ntype Identity struct {\n  Org     curie.IRI `dynamodbav:\"prefix,omitempty\"`\n  ID      curie.IRI `dynamodbav:\"suffix,omitempty\"`\n  Name    string    `dynamodbav:\"name,omitempty\"`\n}\n\n// The type projects: prefix, suffix, name, age \u0026 address.\ntype Person struct {\n  Org     curie.IRI `dynamodbav:\"prefix,omitempty\"`\n  ID      curie.IRI `dynamodbav:\"suffix,omitempty\"`\n  Name    string    `dynamodbav:\"name,omitempty\"`\n  Age     int       `dynamodbav:\"age,omitempty\"`\n  Address string    `dynamodbav:\"address,omitempty\"`\n}\n```\n\n#### Conditional Expression\n\n[Condition expression](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html) helps to implement conditional manipulation of items. The expression defines boolean predicate to determine which items should be modified. If the condition expression evaluates to true, the operation succeeds; otherwise, the operation fails. The library defines a special type `Schema`, which translates a Golang declaration into DynamoDB syntax:\n\n```go\ntype Person struct {\n  Name    string    `dynamodbav:\"name,omitempty\"`\n}\n\n// defines the builder of conditional expression\nvar ifName = ddb.ClauseFor[Person, string](\"Name\")\n\ndb.Update(/* ... */, ifName.NotExists())\ndb.Update(/* ... */, ifName.Eq(\"Verner Pleishner\"))\n```\n\nSee [constraint.go](service/ddb/constraint.go) for the list of supported conditional expressions:\n* Comparison: `Eq`, `Ne`, `Lt`, `Le`, `Gt`, `Ge`, `Is`\n* Unary checks: `Exists`, `NotExists`\n* Set checks: `Between`, `In`\n* String: `HasPrefix`, `Contains`\n* Concurrency control: `Optimistic`\n\nThe conditional expressions are composable using `OneOf` or `AllOf` expression. `OneOf` joins multiple constraint into higher-order constraint that is true when one of defined is true. It is OR expression. `AllOf` is conjunctive join. \n\n```go\ndb.Update(/* ... */,\n  ddb.OneOf(\n    ifName.NotExists(),\n    ifName.Eq(\"Verner Pleishner\"),\n  ),\n)\n\ndb.Update(/* ... */,\n  ddb.AllOf(\n    ifName.HasPrefix(\"Verner\"),\n    ifName.Contains(\"Pleishner\"),\n  ),\n)\n```\n\n#### Update Expression\n\n[Update expression](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html) specifies how update operation will modify the attributes of an item. Unfortunately, this abstraction do not fit into the key-value concept advertised by the library. However, update expression are useful to implement counters, set management, etc. \n\nThe `dynamo` library implements `UpdateWith` method together with simple DSL.\n\n```go\ntype Person struct {\n  Name    string    `dynamodbav:\"name,omitempty\"`\n  Age     int       `dynamodbav:\"age,omitempty\"`\n}\n\n// defines the builder of updater expression\nvar (\n  Name = ddb.UpdateFor[Person, string](\"Name\")\n  Age  = ddb.UpdateFor[Person, int](\"Age\")\n)\n\ndb.UpdateWith(context.Background(),\n  ddb.Updater(\n    Person{\n      Org: curie.IRI(\"University:Kiel\"),\n      ID:  curie.IRI(\"Professor:8980789222\"),\n    },\n    Address.Set(\"Viktoriastrasse 37, Berne, 3013\"),\n    Age.Inc(64),\n  ),\n)\n```\n\n### Optimistic Locking\n\nOptimistic Locking is a lightweight approach to ensure causal ordering of read, write operations to database. AWS made a great post about [Optimistic Locking with Version Number](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBMapper.OptimisticLocking.html).\n\nThe `dynamo` library implements type safe conditional expressions to achieve optimistic locking. This feature is vital when your serverless application concurrently updates same entity in the database.\n\nLet's consider a following example. \n\n```go\ntype Person struct {\n  Org     string `dynamodbav:\"prefix,omitempty\"`\n  ID      string `dynamodbav:\"suffix,omitempty\"`\n  Name    string `dynamodbav:\"anothername,omitempty\"`\n}\n```\n\nAn optimistic locking on this structure is straightforward from DynamoDB perspective. Just make a request with conditional expression:\n\n```golang\n\u0026dynamodb.UpdateItemInput{\n  ConditionExpression: \"anothername = :anothername\",\n  ExpressionAttributeValues: /* \":anothername\" : {S: \"Verner Pleishner\"} */\n}\n```\n\nHowever, the application operates with struct types. 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 library defines set of helper types and functions to declare and use conditional expression in type safe manner:\n\n```go\ntype Person struct {\n  Org     string `dynamodbav:\"prefix,omitempty\"`\n  ID      string `dynamodbav:\"suffix,omitempty\"`\n  Name    string `dynamodbav:\"anothername,omitempty\"`\n}\n\n// defines the builder of conditional expression\nvar Name = ddb.ClauseFor[Person, string](\"Name\")\n\nval, err := db.Update(context.TODO(), \u0026person, Name.Eq(\"Verner Pleishner\"))\nswitch err.(type) {\ncase nil:\n  // success\ncase dynamo.PreConditionFailed:\n  // not found\ndefault:\n  // other i/o error\n}\n```\n\nThe library provides helper function to implement the concurency control. The optimistic locking constraint `Optimistic` is built-in `OneOf` combinator for `NotExists` or `Eq`.\n\n```go\ndb.Put(/* ... */, Name.Optimistic(\"Verner Pleishner\"))\n```\n\nSee the [go doc](https://pkg.go.dev/github.com/fogfish/dynamo?tab=doc) for all supported constraints.\n\n### Batch I/O\n\nThe library supports batch interface to read/write objects from DynamoDB tables:\n* `BatchGet` takes sequence of keys and return sequence of values.\n* `BatchPut` takes sequence of object to store.\n* `BatchRemove` takes sequence of keys to delete.\n\n\n### Configure DynamoDB\n\nThe `dynamo` library is optimized to operate with generic Dynamo DB that declares both partition and sort keys with fixed names. Use the following schema:\n\n\n```typescript\nconst Schema = (): ddb.TableProps =\u003e ({\n  tableName: 'my-table',\n  partitionKey: {type: ddb.AttributeType.STRING, name: 'prefix'},\n  sortKey: {type: ddb.AttributeType.STRING, name: 'suffix'},\n})\n```\n\nIf table uses other names for `partitionKey` and `sortKey` then config options allows to re-declare it.\n\n```go\ndb, err := ddb.New[Person](\n  ddb.WithTable(\"my-table\"),\n\n  // Optionally set Global Secondary Index for the session\n  ddb.WithGlobalSecondaryIndex(\"my-index\"),\n\n  // Optionally declare other keys to be user for attribute projection\n  ddd.WithHashKey(\"someHashKey\"),\n  ddb.WithSortKey(\"someSortKey\"),\n)\n```\n\nThe following [post](example/relational/README.md) discusses in depth and shows example DynamoDB table configuration and covers aspect of secondary indexes. \n\n\n### AWS S3 Support\n\nThe library advances its simple I/O interface to AWS S3 bucket, allowing to persist data types to multiple storage simultaneously.\n\n```go\nimport (\n  \"github.com/fogfish/dynamo/v2/service/ddb\"\n  \"github.com/fogfish/dynamo/v2/service/s3\"\n)\n\n\n//\n// Create client and bind it with DynamoDB the table\ndb, err := ddb.New(ddb.WithTable(\"my-table\"))\n\n//\n// Create client and bind it with S3 bucket\ndb, err := s3.New(s3.WithBucket(\"my-bucket\"))\n```\n\nThere are few fundamental differences about AWS S3 bucket\n* use `s3` schema of connection URI;\n* compose primary key is serialized to S3 bucket path. (e.g. `⟨thread:A, C/E/F⟩ ⟼ thread/A/_/C/E/F`);\n* storage persists struct to JSON, use `json` field tags to specify serialization rules;\n* optimistic locking is not supported yet, any conditional expression is silently ignored;\n* `Update` is not thread safe.\n\n\n\n## How To Contribute\n\nThe library is [MIT](LICENSE) licensed and accepts contributions via GitHub pull requests:\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Added some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\n\nThe build and testing process requires [Go](https://golang.org) version 1.13 or later.\n\n**build** and **test** library.\n\n```bash\ngit clone https://github.com/fogfish/dynamo\ncd dynamo\ngo test ./...\nstaticcheck ./...\n```\n\nUpdate dependency with [go-check-updates](https://github.com/fogfish/go-check-updates)\n\n```bash\ngo-check-updates\ngo-check-updates -u --push github\n``` \n\n### commit message\n\nThe commit message helps us to write a good release note, speed-up review process. The message should address two question what changed and why. The project follows the template defined by chapter [Contributing to a Project](http://git-scm.com/book/ch5-2.html) of Git book.\n\n### bugs\n\nIf you experience any issues with the library, please let us know via [GitHub issues](https://github.com/fogfish/dynamo/issue). We appreciate detailed and accurate reports that help us to identity and replicate the issue. \n\n## License\n\n[![See LICENSE](https://img.shields.io/github/license/fogfish/dynamo.svg?style=for-the-badge)](LICENSE)\n","funding_links":[],"categories":["Database Drivers","数据库驱动程序","Data Integration Frameworks"],"sub_categories":["Interfaces to Multiple Backends","多个后端接口"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffogfish%2Fdynamo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffogfish%2Fdynamo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffogfish%2Fdynamo/lists"}