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拉取请求接受贡献:

  1. Fork它
  2. 创建您的功能分支 (git checkout -b my-new-feature)
  3. 提交您的更改 (git commit -am 'Added some feature')
  4. 推送到分支 (git push origin my-new-feature)
  5. 创建新的拉取请求

构建和测试过程需要Go 1.13或更高版本。

许可证

MIT许可证


更多关于golang AWS存储服务键值抽象化插件库dynamo的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于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()

最佳实践

  1. 合理设计主键:DynamoDB 性能很大程度上取决于主键设计
  2. 使用适当容量模式:根据需要选择按需或预置容量
  3. 错误处理:特别注意处理 ConditionalCheckFailedException
  4. 重试机制:实现适当的重试逻辑处理暂时性故障
  5. 监控:设置适当的 CloudWatch 警报监控表指标

通过 dynamo 库,我们可以用更简洁的代码实现 DynamoDB 的键值存储功能,同时保持类型安全和易用性。

回到顶部