michimani.net

AWS SDK for Go で DynamoDB のテーブルから特定の属性だけ取得する

2020-05-18

最近は Go を書くことが多いので、備忘録として AWS SDK for Go を使って DynamoDB のテーブルから特定の属性 (Attributes) のみを取得する方法について書いておきます。

目次

Go で DynamoDB を操作する

Go で DynamoDB を操作する方法についてぐぐってみると、 guregu/dynamo というライブラリを使った例が多く出てきます。DynamoDB に限らず、 Go の SDK で AWS のリソースを操作する際にはポインタ型を扱う必要があるため、実装が複雑だと感じてしまいがちです。 guregu/dynamo を使用すると、非常にかんたんな記述で DynamoDB を操作することができます。

ただし、今回のような場合や少し複雑な使い方をする場合は AWS SDK for Go を使ったほうが柔軟な実装ができます。

やること

DynamoDB のサンプルテーブルの Thread テーブルに対して、次の 2 つの方法でデータを取得してみます。

サンプルデータについては公式のデータを使用します。

ここからは実際のコードを抜粋して書きますが、全体については Gist に置いていますのでそちらを参照してください。

すべての属性を含めて Scan する

package main

import (
	"bytes"
	"encoding/json"
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

// Thread is a struct of a Thread item
type Thread struct {
	ForumName          string   `dynamodbav:"ForumName" json:"forum_name"`
	Subject            string   `dynamodbav:"Subject" json:"subject"`
	Answered           int      `dynamodbav:"Answered" json:"answered"`
	LastPostedBy       string   `dynamodbav:"LastPostedBy" json:"last_posted_by"`
	LastPostedDateTime string   `dynamodbav:"LastPostedDateTime" json:"last_posted_date_time"`
	Message            string   `dynamodbav:"Message" json:"message"`
	Replies            int      `dynamodbav:"Replies" json:"replies"`
	Tags               []string `dynamodbav:"Tags" json:"tags"`
	Views              int      `dynamodbav:"Views" json:"views"`
}

var tableName string = "Thread"
var region string = "ap-northeast-1"
var db = dynamodb.New(session.New(), &aws.Config{
	Region: aws.String(region),
})

// Scan is a function to scan table.
func Scan() []Thread {
	var threads []Thread = []Thread{}
	scanOut, err := db.Scan(&dynamodb.ScanInput{
		TableName: aws.String(tableName),
	})

	if err != nil {
		fmt.Println(err.Error())
		return threads
	}

	for _, scanedThread := range scanOut.Items {
		var threadTmp Thread
		_ = dynamodbattribute.UnmarshalMap(scanedThread, &threadTmp)
		threads = append(threads, threadTmp)
	}

	return threads
}

// ThreadsToJSONString is a function to convert Thread object to JSON string.
func ThreadsToJSONString(threads interface{}) string {
	j, err := json.Marshal(threads)

	if err != nil {
		fmt.Println(err.Error())
		return ""
	}

	var buf bytes.Buffer
	jerr := json.Indent(&buf, j, "", " ")

	if jerr != nil {
		fmt.Println(jerr.Error())
		return ""
	}

	return buf.String()
}

func main() {
	fmt.Println("Scan with all attributes.")
	allAttrRes := Scan()
	fmt.Println(ThreadsToJSONString(allAttrRes))
}

まず Thread Struct をタグ付きで定義します。 dynamodbav タグは DynamoDB テーブルの属性名を指定します。 json タグは JSON で出力する際の属性名を指定します。 Scan() では引数に *dynamodb.ScanInput 型の変数を渡します。その変数ではテーブル名 TableName のみ指定しています。

ThreadsToJSONString() 関数では Thread オブジェクトを整形済みの JSON 文字列に変換しています。

このスクリプトを実行すると、次のような出力が得られます。

$ go run ./src/dynamo/main.go
Scan with all attributes.
[
 {
  "forum_name": "Amazon CloudFront",
  "subject": "CloudFront Thread 1",
  "answered": 0,
  "last_posted_by": "User A",
  "last_posted_date_time": "2015-09-22T19:58:22.514Z",
  "message": "CloudFront thread 1 message",
  "replies": 0,
  "tags": [
   "index",
   "primarykey",
   "table"
  ],
  "views": 10
 },
 {
  "forum_name": "Amazon S3",
  "subject": "S3 Thread 1",
  "answered": 0,
  "last_posted_by": "User A",
  "last_posted_date_time": "2015-09-29T19:58:22.514Z",
  "message": "S3 thread 1 message",
  "replies": 0,
  "tags": [
   "largeobjects",
   "multipart upload"
  ],
  "views": 0
 },
 {
  "forum_name": "Amazon DynamoDB",
  "subject": "DynamoDB Thread 1",
  "answered": 0,
  "last_posted_by": "User A",
  "last_posted_date_time": "2015-09-22T19:58:22.514Z",
  "message": "DynamoDB thread 1 message",
  "replies": 0,
  "tags": [
   "index",
   "primarykey",
   "table"
  ],
  "views": 0
 },
 {
  "forum_name": "Amazon DynamoDB",
  "subject": "DynamoDB Thread 2",
  "answered": 0,
  "last_posted_by": "User A",
  "last_posted_date_time": "2015-09-15T19:58:22.514Z",
  "message": "DynamoDB thread 2 message",
  "replies": 0,
  "tags": [
   "items",
   "attributes",
   "throughput"
  ],
  "views": 0
 }
]

特定の属性のみを取得するように Scan する

基本的には上の例と同じなので、異なる部分のみ抜き出しています。今回は Thread テーブルの属性のうち、 ForumNameSubject のみ取得してみます。

// ThreadWithSomeAttr is a struct of a Thread item with some attributes.
type ThreadWithSomeAttr struct {
	ForumName string `dynamodbav:"ForumName" json:"forum_name"`
	Subject   string `dynamodbav:"Subject" json:"subject"`
}

// ScanSomeAttr is a function to scan table with some attributes.
func ScanSomeAttr() []ThreadWithSomeAttr {
	var threads []ThreadWithSomeAttr = []ThreadWithSomeAttr{}
	scanOut, err := db.Scan(&dynamodb.ScanInput{
		TableName: aws.String(tableName),
		ExpressionAttributeNames: map[string]*string{
			"#FNAME": aws.String("ForumName"),
			"#SUBJ":  aws.String("Subject"),
		},
		ProjectionExpression: aws.String("#FNAME, #SUBJ"),
	})

	if err != nil {
		fmt.Println(err.Error())
		return threads
	}

	for _, scanedThread := range scanOut.Items {
		var threadTmp ThreadWithSomeAttr
		_ = dynamodbattribute.UnmarshalMap(scanedThread, &threadTmp)
		threads = append(threads, threadTmp)
	}

	return threads
}

Scan した結果を受け取る Struct として ThreadWithSomeAttr を定義します。Scan() の引数としては、テーブル名に加えて、取得したい属性名を指定した *dynamodb.ScanInput を指定しています。

この状態で実行すると、次のような出力が得られます。

$ go run ./src/dynamo/main.go
Scan with some attributes.
[
 {
  "forum_name": "Amazon CloudFront",
  "subject": "CloudFront Thread 1"
 },
 {
  "forum_name": "Amazon S3",
  "subject": "S3 Thread 1"
 },
 {
  "forum_name": "Amazon DynamoDB",
  "subject": "DynamoDB Thread 1"
 },
 {
  "forum_name": "Amazon DynamoDB",
  "subject": "DynamoDB Thread 2"
 }
]

ちなみに Scan した結果を ThreadWithSomeAttr Struct の代わりに、すべての属性を定義した Thread で受け取ると、次のような結果になります。

$ go run ./src/dynamo/main.go
Scan with some attributes.
[
 {
  "forum_name": "Amazon CloudFront",
  "subject": "CloudFront Thread 1",
  "answered": 0,
  "last_posted_by": "",
  "last_posted_date_time": "",
  "message": "",
  "replies": 0,
  "tags": null,
  "views": 0
 },
 {
  "forum_name": "Amazon S3",
  "subject": "S3 Thread 1",
  "answered": 0,
  "last_posted_by": "",
  "last_posted_date_time": "",
  "message": "",
  "replies": 0,
  "tags": null,
  "views": 0
 },
 {
  "forum_name": "Amazon DynamoDB",
  "subject": "DynamoDB Thread 1",
  "answered": 0,
  "last_posted_by": "",
  "last_posted_date_time": "",
  "message": "",
  "replies": 0,
  "tags": null,
  "views": 0
 },
 {
  "forum_name": "Amazon DynamoDB",
  "subject": "DynamoDB Thread 2",
  "answered": 0,
  "last_posted_by": "",
  "last_posted_date_time": "",
  "message": "",
  "replies": 0,
  "tags": null,
  "views": 0
 }
]

ForumNameSubject の値は取得できますが、それ以外の属性についてはそれぞれの属性に定義された型の初期値になります。

まとめ

AWS SDK for Go を使って DynamoDB のテーブルから特定の属性 (Attributes) のみを取得してみた話でした。
ポインタ型については 1 年目のころに C 言語で扱って以来だったのでちょっと拒否反応がありましたが、慣れれば特に問題ないですね。

AWS SDK に関しては公式ドキュメントもしっかり用意されているので、その点でも安心です。

今回のサンプルスクリプトは下記 Gist に置いています。


comments powered by Disqus