Gemcook Tech Blog
🦜

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とは

https://github.com/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の基本的な内容がまとまっていて分かりやすかったです。

https://qiita.com/guregu/items/2e9ac305791935b86f5d

2025年9月現在、ライブラリのリリースから約10年を迎えますが、メンテナンスは今も継続的に行われているようです。

https://x.com/guregu/status/1802109040249708989

最小限のプロジェクトを作って、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を実行できるか確認します。

main.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のコードをほぼそのまま使います。リージョンだけ東京リージョンに変えます。

main.go
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に追加します。

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構造体を追加します。

main.go
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操作を試してみます。

main.go
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だけを複数実行してみます。コードを以下のように修正して・・・

main.go
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コントリビュートから…)

この記事がどなたかの参考になれば幸いです。

Gemcook Tech Blog
Gemcook Tech Blog

Discussion