golang AWS存储服务键值抽象化插件库dynamo的使用
golang AWS存储服务键值抽象化插件库dynamo的使用
简介
dynamo库实现了一个简单的键值抽象,用于在AWS存储服务(AWS DynamoDB和AWS S3)上存储代数、关联数据类型。
该库鼓励开发者使用Golang结构体定义领域模型,编写正确、可维护的代码。使用这个库,应用程序可以实现理想的数据模型,只需对DynamoDB发出单个请求即可建模一对一、一对多甚至多对多的关系。
快速开始
数据类型定义
数据类型定义是使用dynamo库开发的基本部分。Golang结构体声明了应用程序的领域。公共字段被序列化为DynamoDB属性,字段标签dynamodbav
控制编组/解组过程。
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"`
}
// 实现Thing接口
func (p Person) HashKey() curie.IRI { return p.Org }
func (p Person) SortKey() curie.IRI { return p.ID }
// 创建实例
var person := Person{
Org: curie.IRI("University:Kiel"),
ID: curie.IRI("Professor:8980789222"),
Name: "Verner Pleishner",
Age: 64,
Address: "Blumenstrasse 14, Berne, 3013",
}
DynamoDB I/O
以下是典型的I/O模式示例:
import (
"github.com/fogfish/dynamo/v2/service/ddb"
)
// 创建dynamodb客户端并绑定表
db, err := ddb.New[Person](ddb.WithTable("my-table"))
// 写入结构体
if err := db.Put(context.TODO(), person); err != nil {
// 处理错误
}
// 查找结构体
val, err := db.Get(context.TODO(),
Person{
Org: curie.IRI("University:Kiel"),
ID: curie.IRI("Professor:8980789222"),
},
)
// 应用部分更新
val, err := db.Update(context.TODO(),
Person{
Org: curie.IRI("University:Kiel"),
ID: curie.IRI("Professor:8980789222"),
Address: "Viktoriastrasse 37, Berne, 3013",
}
)
// 删除结构体
_, err := db.Remove(context.TODO(),
Person{
Org: curie.IRI("University:Kiel"),
ID: curie.IRI("Professor:8980789222"),
}
)
错误处理
库强制执行"根据行为而非类型断言错误"的错误处理策略:
type ErrorCode interface{ ErrorCode() string }
type NotFound interface { NotFound() string }
type PreConditionFailed interface { PreConditionFailed() bool }
type Conflict interface { Conflict() bool }
type Gone interface { Gone() bool }
层次结构
库支持定义数据元素之间的A ⟼ B
关系。考虑消息线程作为这种层次结构的经典示例:
// 使用分区键匹配DynamoDB条目
db.Match(context.TODO(), Message{Thread: "thread:A"})
// 使用分区键和部分排序键匹配DynamoDB条目
db.Match(context.TODO(), Message{Thread: "thread:A", ID: "C"})
序列和分页
层次结构是组织集合、列表、集合等的方式。Match
返回一个惰性序列,表示整个集合。有时需要将集合分成一系列页面。
// 1. 在流上设置限制
seq, cursor, err := db.Match(context.TODO(),
Message{Thread: "thread:A", ID: "C"},
dynamo.Limit(25),
)
// 2. 使用游标继续I/O
seq := db.Match(context.TODO(),
Message{Thread: "thread:A", ID: "C"},
dynamo.Limit(25),
cursor,
)
关联数据
结构化数据的交叉链接是类型安全领域驱动设计的重要组成部分。库帮助开发者使用熟悉的数据类型建模数据实例之间的关系。
type Person struct {
Org curie.IRI `dynamodbav:"prefix,omitempty"`
ID curie.IRI `dynamodbav:"suffix,omitempty"`
Leader *curie.IRI `dynamodbav:"leader,omitempty"`
}
类型投影
通常,应用程序中已建立类型系统。将依赖项注入dynamo库并不方便。此外,二级索引的使用需要核心类型的多个投影。
// 原始核心类型
type Person struct {
Org string `dynamodbav:"prefix,omitempty"`
ID string `dynamodbav:"suffix,omitempty"`
Name string `dynamodbav:"name,omitempty"`
Age int `dynamodbav:"age,omitempty"`
Country string `dynamodbav:"country,omitempty"`
}
// 使用⟨Org, ID⟩作为复合键的核心类型投影
type dbPerson Person
func (p dbPerson) HashKey() curie.IRI { return curie.IRI(p.Org) }
func (p dbPerson) SortKey() curie.IRI { return curie.IRI(p.ID) }
// 使用⟨Org, Name⟩作为复合键的核心类型投影
type dbNamedPerson Person
func (p dbNamedPerson) HashKey() curie.IRI { return curie.IRI(p.Org) }
func (p dbNamedPerson) SortKey() curie.IRI { return curie.IRI(p.Name) }
// 使用⟨Country, Name⟩作为复合键的核心类型投影
type dbCitizen Person
func (p dbCitizen) HashKey() curie.IRI { return curie.IRI(p.Country) }
func (p dbCitizen) SortKey() curie.IRI { return curie.IRI(p.Name) }
DynamoDB表达式
投影表达式
投影表达式定义要从表中读取的属性。库自动为每个请求定义投影表达式。表达式从数据类型定义派生。
条件表达式
条件表达式帮助实现项目的条件操作。表达式定义布尔谓词以确定应修改哪些项目。如果条件表达式评估为true,则操作成功;否则操作失败。
type Person struct {
Name string `dynamodbav:"name,omitempty"`
}
// 定义条件表达式构建器
var ifName = ddb.ClauseFor[Person, string]("Name")
db.Update(/* ... */, ifName.NotExists())
db.Update(/* ... */, ifName.Eq("Verner Pleishner"))
更新表达式
更新表达式指定更新操作将如何修改项目的属性。不幸的是,这个抽象不适合库宣传的键值概念。然而,更新表达式对于实现计数器、集合管理等很有用。
type Person struct {
Name string `dynamodbav:"name,omitempty"`
Age int `dynamodbav:"age,omitempty"`
Hobbies []string `dynamodbav:"hobbies,omitempty"`
}
// 定义更新表达式构建器
var (
Name = ddb.UpdateFor[Person, string]("Name")
Age = ddb.UpdateFor[Person, int]("Age")
Hobbies = ddb.UpdateFor[Person, []string]("Hobbies")
)
db.UpdateWith(context.Background(),
ddb.Updater(
Person{
Org: curie.IRI("University:Kiel"),
ID: curie.IRI("Professor:8980789222"),
},
Address.Set("Viktoriastrasse 37, Berne, 3013"),
Age.Inc(64),
),
)
乐观锁定
乐观锁定是一种轻量级方法,用于确保对数据库的读、写操作的因果顺序。
type Person struct {
Name string `dynamodbav:"anothername,omitempty"`
}
// 定义条件表达式构建器
var Name = ddb.ClauseFor[Person, string]("Name")
val, err := db.Update(context.TODO(), &person, Name.Eq("Verner Pleishner"))
switch err.(type) {
case nil:
// 成功
case dynamo.PreConditionFailed:
// 未找到
default:
// 其他I/O错误
}
批处理I/O
库支持批处理接口从DynamoDB表读取/写入对象:
BatchGet
获取键序列并返回值序列BatchPut
获取要存储的对象序列BatchRemove
获取要删除的键序列
配置DynamoDB
库经过优化,可与声明分区键和具有固定名称的排序键的通用Dynamo DB一起使用。使用以下模式:
db, err := ddb.New[Person](
ddb.WithTable("my-table"),
// 可选设置会话的全局二级索引
ddb.WithGlobalSecondaryIndex("my-index"),
// 可选声明用于属性投影的其他键
ddd.WithHashKey("someHashKey"),
ddb.WithSortKey("someSortKey"),
)
AWS S3支持
库将其简单的I/O接口扩展到AWS S3存储桶,允许同时将数据类型持久化到多个存储。
import (
"github.com/fogfish/dynamo/v2/service/ddb"
"github.com/fogfish/dynamo/v2/service/s3"
)
// 创建客户端并绑定DynamoDB表
db, err := ddb.New(ddb.WithTable("my-table"))
// 创建客户端并绑定S3存储桶
db, err := s3.New(s3.WithBucket("my-bucket"))
如何贡献
该库采用MIT许可,并通过GitHub拉取请求接受贡献:
- Fork它
- 创建您的功能分支 (
git checkout -b my-new-feature
) - 提交您的更改 (
git commit -am 'Added some feature'
) - 推送到分支 (
git push origin my-new-feature
) - 创建新的拉取请求
构建和测试过程需要Go 1.13或更高版本。
许可证
MIT许可证
更多关于golang AWS存储服务键值抽象化插件库dynamo的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang AWS存储服务键值抽象化插件库dynamo的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang 中使用 AWS DynamoDB 的键值存储抽象化
DynamoDB 是 AWS 提供的高性能 NoSQL 数据库服务,非常适合键值存储场景。在 Go 中,我们可以使用官方 AWS SDK 或第三方库来简化 DynamoDB 操作。
官方 AWS SDK 基础使用
首先安装 AWS SDK:
go get github.com/aws/aws-sdk-go-v2
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/dynamodb
基本示例
package main
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
func main() {
// 加载默认配置
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Fatalf("无法加载配置: %v", err)
}
// 创建DynamoDB客户端
client := dynamodb.NewFromConfig(cfg)
// 定义表名和键
tableName := "MyKeyValueTable"
key := "user123"
value := "John Doe"
// 写入数据
err = putItem(client, tableName, key, value)
if err != nil {
log.Fatalf("写入数据失败: %v", err)
}
// 读取数据
result, err := getItem(client, tableName, key)
if err != nil {
log.Fatalf("读取数据失败: %v", err)
}
fmt.Printf("获取的值: %s\n", result)
}
// 写入数据到DynamoDB
func putItem(client *dynamodb.Client, tableName, key, value string) error {
_, err := client.PutItem(context.TODO(), &dynamodb.PutItemInput{
TableName: aws.String(tableName),
Item: map[string]types.AttributeValue{
"Key": &types.AttributeValueMemberS{Value: key},
"Value": &types.AttributeValueMemberS{Value: value},
},
})
return err
}
// 从DynamoDB读取数据
func getItem(client *dynamodb.Client, tableName, key string) (string, error) {
result, err := client.GetItem(context.TODO(), &dynamodb.GetItemInput{
TableName: aws.String(tableName),
Key: map[string]types.AttributeValue{
"Key": &types.AttributeValueMemberS{Value: key},
},
})
if err != nil {
return "", err
}
if result.Item == nil {
return "", fmt.Errorf("未找到键 %s", key)
}
val, ok := result.Item["Value"].(*types.AttributeValueMemberS)
if !ok {
return "", fmt.Errorf("值类型不匹配")
}
return val.Value, nil
}
使用 dynamo 第三方库简化操作
dynamo 是一个流行的 DynamoDB 客户端库,提供了更简洁的 API。
安装:
go get github.com/guregu/dynamo
dynamo 示例
package main
import (
"fmt"
"log"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/guregu/dynamo"
)
// 定义数据结构
type Item struct {
Key string `dynamo:"Key,hash"` // 分区键
Value string `dynamo:"Value"`
UpdatedAt time.Time `dynamo:"UpdatedAt"`
}
func main() {
// 加载AWS配置
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Fatalf("无法加载配置: %v", err)
}
// 创建dynamo DB实例
db := dynamo.NewFromConfig(cfg)
table := db.Table("MyKeyValueTable")
// 写入数据
item := Item{
Key: "user123",
Value: "John Doe",
UpdatedAt: time.Now(),
}
err = table.Put(item).Run()
if err != nil {
log.Fatalf("写入失败: %v", err)
}
// 读取数据
var result Item
err = table.Get("Key", "user123").One(&result)
if err != nil {
log.Fatalf("读取失败: %v", err)
}
fmt.Printf("获取的值: %+v\n", result)
// 更新数据
err = table.Update("Key", "user123").
Set("Value", "Jane Smith").
Set("UpdatedAt", time.Now()).
Run()
if err != nil {
log.Fatalf("更新失败: %v", err)
}
// 删除数据
err = table.Delete("Key", "user123").Run()
if err != nil {
log.Fatalf("删除失败: %v", err)
}
}
高级功能
1. 批量操作
// 批量写入
items := []Item{
{Key: "user1", Value: "Alice", UpdatedAt: time.Now()},
{Key: "user2", Value: "Bob", UpdatedAt: time.Now()},
{Key: "user3", Value: "Charlie", UpdatedAt: time.Now()},
}
var writeBatch []dynamo.Put
for _, item := range items {
writeBatch = append(writeBatch, table.Put(item))
}
err := db.Batch().Write(writeBatch...).Run()
if err != nil {
log.Fatalf("批量写入失败: %v", err)
}
// 批量读取
var results []Item
err = table.Batch("Key").
Get("user1", "user2", "user3").
All(&results)
if err != nil {
log.Fatalf("批量读取失败: %v", err)
}
2. 条件更新
// 只有当前值为"John Doe"时才更新
err = table.Update("Key", "user123").
Set("Value", "John Smith").
If("Value = ?", "John Doe").
Run()
if err != nil {
log.Printf("条件更新失败(可能条件不满足): %v", err)
}
3. TTL (生存时间)
type ItemWithTTL struct {
Key string `dynamo:"Key,hash"`
Value string `dynamo:"Value"`
ExpiresAt int64 `dynamo:"ExpiresAt,ttl"` // TTL字段
}
// 写入带TTL的数据
item := ItemWithTTL{
Key: "tempData",
Value: "This will expire",
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
}
err := db.Table("TTLTable").Put(item).Run()
最佳实践
- 合理设计主键:DynamoDB 性能很大程度上取决于主键设计
- 使用适当容量模式:根据需要选择按需或预置容量
- 错误处理:特别注意处理 ConditionalCheckFailedException
- 重试机制:实现适当的重试逻辑处理暂时性故障
- 监控:设置适当的 CloudWatch 警报监控表指标
通过 dynamo 库,我们可以用更简洁的代码实现 DynamoDB 的键值存储功能,同时保持类型安全和易用性。