guregu/dynamoに入門する
こんにちは、生チュロスに最近ハマっている村中(@wage790)です。
普段バックエンドをGoで開発しており、いま担当しているプロジェクトでは、DynamoDBとのやり取りにguregu/dynamoというサードパーティのライブラリを使用しています。
自分のポリシーとして、仕事で使うライブラリはなるべく自分の手元でもゼロからインストールして試してみて、基礎から理解するようにしたいと考えています。
そこで今回は、guregu/dynamoの挙動を確認するため、最小限のプロジェクトを作って基本的なCRUD操作を試してみます。
対象読者
- guregu/dynamoに入門したい
- Goの基礎はなんとなく分かってる
→ Goは静的型付けのコンパイル型手続き型言語、main関数がエントリーポイントだよ、エラーハンドリングが明示的だよね〜etc
この記事で扱うこと
- guregu/dynamoの挙動を確認するため、最小限のプロジェクトを作ってCRUD操作してみる
この記事で扱わないこと
- DynamoDB/NoSQLの設計思想
- フロントエンド
- API経由でのCRUD操作
- TerraformやCloudFormationなどIaC
guregu/dynamoとは
guregu/dynamoは、Go向けのDynamoDBラッパーライブラリ。
公式SDK(aws-sdk-go)を直接使うとリクエストの組み立てが冗長になりがちなところ、guregu/dynamoを使うと、より直感的・宣言的にDynamoDBを操作できます。
guregu/dynamo と aws-sdk-go をコードで比較
↓ guregu/dynamoでcreateした場合
// --- guregu/dynamo ---
user := User{UserID: "25", Name: "TSUTSUGOH"}
if err := table.Put(user).Run(); err != nil {
fmt.Println("Create error:", err)
}
↓ aws-sdk-goでcreateした場合
// --- aws-sdk-go (v2) ---
import "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
user := User{UserID: "25", Name: "TSUTSUGOH"}
av, _ := attributevalue.MarshalMap(user)
_, err := svc.PutItem(ctx, &dynamodb.PutItemInput{
TableName: aws.String("Users"),
Item: av,
// 上書き防止したい場合
// ConditionExpression: aws.String("attribute_not_exists(UserID)"),
})
if err != nil {
fmt.Println("Create error:", err)
}
ライブラリ作者のgureguさんの解説記事は、2015年の記事ですが、guregu/dynamoの基本的な内容がまとまっていて分かりやすかったです。
2025年9月現在、ライブラリのリリースから約10年を迎えますが、メンテナンスは今も継続的に行われているようです。
最小限のプロジェクトを作って、guregu/dynamoを試してみる
※ AWS CLI がインストールされ、aws configure
で認証情報とリージョン設定が済んでいる前提で進めます。
※ Goのバージョンは1.24.3を使用しています。
プロジェクト作成
まずは、プロジェクトを作っていきます。プロジェクト名はなんでもいいですが、ここではgo-dynamo-guregu
としました。
mkdir go-dynamo-guregu
cd go-dynamo-guregu
go mod init example.com/go-dynamo-guregu
touch main.go
何はともあれHello World
問題なくGoを実行できるか確認します。
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
go run main.go
で実行してみます。
$ go run main.go
Hello World!
これでGoの準備は完了できました。ここからDynamoDBを触るコードに発展させていきます。
ライブラリのインストール
guregu/dynamoをインストールします。
go get github.com/guregu/dynamo
DynamoDB 接続準備
DynamoDBに接続するための準備(セッションとクライアントの作成)を行います。
このあたりはguregu/dynamoのREADMEにあるExampleのコードをほぼそのまま使います。リージョンだけ東京リージョンに変えます。
package main
import (
- "fmt"
+ "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/aws-sdk-go/aws/session"
+ "github.com/guregu/dynamo"
)
func main() {
- fmt.Println("Hello World!")
+ // DynamoDB セッション初期化
+ sess := session.Must(session.NewSession())
+
+ // DynamoDB を操作するためのクライアント作成
+ db := dynamo.New(sess, &aws.Config{Region: aws.String("ap-northeast-1")})
}
テーブル作成 & 取得
今回はUsersテーブルを作成します。コード側からテーブルを作成してもいいですが、今回はCRUD操作がメインのやりたいことなので、テーブルはマネコンからサッと作っちゃいます。
パーティションキーにUserID
を設定し、その他の値(Name
など)は通常の属性として持たせるシンプルなテーブルです。
いったんはこのままのシンプルな構成で。
今後「UserID
+ Name
の組み合わせで一意にしたい」「UserID
ごとにName
で並べ替えたい」などと拡張したい場合には、ソートキーにName
を追加して複合キー構成にします。
はい。Usersテーブルを作成できました。
これを取得する処理をmain.go
に追加します。
package main
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/guregu/dynamo"
)
func main() {
// DynamoDB セッション初期化
sess := session.Must(session.NewSession())
// DynamoDB を操作するためのクライアント作成
db := dynamo.New(sess, &aws.Config{Region: aws.String("ap-northeast-1")})
+ // Users テーブルを取得
+ table := db.Table("Users")
}
ここでやっていることは、
-
db
= DynamoDB 全体 -
db.Table("Users")
= その中の Usersテーブル- 何度も使うので変数
table
に代入しておく
- 何度も使うので変数
という感じです。
ここから.Put
,.Get
,.Update
,.Delete
をメソッドチェーンでつなげることで、CRUD操作ができるという流れです。(後述します)
User構造体を追加
DynamoDBのレコードをGoで扱いやすくするために、User構造体を追加します。
package main
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/guregu/dynamo"
)
+type User struct {
+ UserID string `dynamo:"UserID,hash"`
+ Name string `dynamo:"Name"`
+}
func main() {
// DynamoDB セッション初期化
sess := session.Must(session.NewSession())
// DynamoDB を操作するためのクライアント作成
db := dynamo.New(sess, &aws.Config{Region: aws.String("ap-northeast-1")})
// Users テーブルを取得
table := db.Table("Users")
}
dynamo:"UserID,hash"
の hash
は、このフィールドが パーティションキー(主キー) になることを示しています。DynamoDB ではhash
が主キー、range
がソートキーを表します。
CRUD操作をひと通り試す
いよいよ今回の本題です。基本的なCRUD操作を試してみます。
package main
import (
+ "fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/guregu/dynamo"
)
type User struct {
UserID string `dynamo:"UserID,hash"`
Name string `dynamo:"Name"`
}
func main() {
// DynamoDB セッション初期化
sess := session.Must(session.NewSession())
// DynamoDB を操作するためのクライアント作成
db := dynamo.New(sess, &aws.Config{Region: aws.String("ap-northeast-1")})
// Users テーブルを取得
table := db.Table("Users")
+ // Create
+ user := User{UserID: "25", Name: "TSUTSUGOH"}
+ if err := table.Put(user).Run(); err != nil {
+ fmt.Println("Create error:", err)
+ return
+ }
+ fmt.Println("User created:", user.UserID)
+ // Read
+ var result User
+ if err := table.Get("UserID", "25").One(&result); err != nil {
+ fmt.Println("Read error:", err)
+ return
+ }
+ fmt.Println("User retrieved:", result)
+ // Update
+ if err := table.Update("UserID", "25").Set("Name", "TSUTSUGOH Updated").Run(); err != nil {
+ fmt.Println("Update error:", err)
+ return
+ }
+ fmt.Println("User updated:", "25", "→", "TSUTSUGOH Updated")
+ // Delete
+ if err := table.Delete("UserID", "25").Run(); err != nil {
+ fmt.Println("Delete error:", err)
+ return
+ }
+ fmt.Println("User deleted:", "25")
}
いったん全てのCRUDを追加しています。
- Create →
Put
- RDBのようなINSERTの概念はなく、Putが「新規作成 or 上書き」に対応します
- Read →
Get
- 主キーで1件取得する
- 他に全件検索のScanなども利用可能
- Update →
Update
- Setメソッドをチェーンして複数項目をまとめて更新できる
- Delete →
Delete
- 主キーで指定したレコードを削除
実行!
go run main.go
で実行すると、ターミナルのログコンソールに以下のように表示され、CRUDが順番に実行されていますね!おおお!
$ go run main.go
User created: 25
User retrieved: {25 TSUTSUGOH}
User updated: 25 → TSUTSUGOH Updated
User deleted: 25
Createだけを複数実行してみる
テーブルにアイテムが追加されたことが分かりやすいように、createだけを複数実行してみます。コードを以下のように修正して・・・
package main
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/guregu/dynamo"
)
type User struct {
UserID string `dynamo:"UserID,hash"`
Name string `dynamo:"Name"`
}
func main() {
// DynamoDB セッション初期化
sess := session.Must(session.NewSession())
// DynamoDB を操作するためのクライアント作成
db := dynamo.New(sess, &aws.Config{Region: aws.String("ap-northeast-1")})
// Users テーブルを取得
table := db.Table("Users")
+ users := []User{
+ {UserID: "1", Name: "SUZUKI"},
+ {UserID: "4", Name: "ITAKURA"},
+ {UserID: "3", Name: "WATANABE"},
+ {UserID: "22", Name: "SEKO"},
+ {UserID: "6", Name: "ENDO"},
+ {UserID: "8", Name: "MINAMINO"},
+ {UserID: "15", Name: "KAMADA"},
+ {UserID: "7", Name: "MITOMA"},
+ {UserID: "10", Name: "DOUAN"},
+ {UserID: "20", Name: "KUBO"},
+ {UserID: "9", Name: "UEDA"},
+ }
+
+ for _, user := range users {
+ if err := table.Put(user).Run(); err != nil {
+ fmt.Println("Create error:", err)
+ return
+ }
+ fmt.Println("User created:", user.UserID, user.Name)
+ }
}
実行!
$ go run main.go
User created: 1 SUZUKI
User created: 4 ITAKURA
User created: 3 WATANABE
User created: 22 SEKO
User created: 6 ENDO
User created: 8 MINAMINO
User created: 15 KAMADA
User created: 7 MITOMA
User created: 10 DOUAN
User created: 20 KUBO
User created: 9 UEDA
OK。マネコンでUsersテーブルを確認すると、ユーザーたちが追加されています。
なるほど、guregu/dynamoを使えば簡単にCRUD操作できるんですね。ここから拡張していけばもっといろんなことできそうですが、今回はいったんここまで。
まとめ
というわけで、CRUD操作を通じて、guregu/dynamoの基本的な挙動を確認できました。
guregu/dynamoはメソッドチェーンで宣言的にCRUDを書けるため、RubyやJSのORMに慣れている人には自然でフィットしやすいと感じるかもしれません。
型安全やエラーハンドリングといった部分はしっかりGo的であり、スクリプト言語的な直感性とGo的な堅実さをうまく両立した便利なライブラリ、といったところでしょうか。
・・・
それにしても、こうしたライブラリを作って公開されている方は本当にすごいな〜と思います。自分もゆくゆくは挑戦してみたいです。(まずはOSSコントリビュートから…)
この記事がどなたかの参考になれば幸いです。
Discussion