0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Golangはじめて物語(第8話: DynamoDBのデータモデリングのベストプラクティスに従った場合の実装方法)

Posted at

はじめに

最近、DynamoDB のデータモデリングを勉強しているが、これに従ってみようとすると、あれ?なんか Golang での実装が難しくないか?と思って調べてみた。
※Python だったらディクショナリ型を使えばお手軽に扱える気がするのだけど。

データモデリングのベストプラクティスについては、以下の記事を参考にすると良い。

何が難しいか?

いずれの記事でも、同じハッシュキーにできるものは1つのテーブルに寄せて、属性が異なるものはソートキーで種別を分ける、と書かれている。
例えば、あるハッシュキーに紐づく情報をqueryで一発で引くと、以下のように personalcompany といった異なる属性を持ったアイテムが取得されることになる。

{
    "Items": [
        {
            "department": {
                "S": "sales"
            },
            "grade": {
                "S": "A"
            },
            "id": {
                "S": "00001"
            },
            "type": {
                "S": "company"
            }
        },
        {
            "birthdt": {
                "S": "19800101"
            },
            "postalcode": {
                "S": "1740001"
            },
            "id": {
                "S": "00001"
            },
            "telno": {
                "S": "09000000000"
            },
            "name": {
                "S": "Taro"
            },
            "type": {
                "S": "personal"
            }
        }
    ],
    "Count": 2,
    "ScannedCount": 2,
    "ConsumedCapacity": null
}

これを Golang で実装しようとした場合、もちろん GetItem() を2回呼ぶという方法もあるが、それだとコードが冗長になってしまうケースがある。Query() で一発で取得しようとしたらどうしたら良いだろうか?

考えてみる

上記のJSONを、JSON-to-Go に食わせてみると、以下のように出力される。

type AutoGenerated struct {
	Items []struct {
		Department struct {
			S string `json:"S"`
		} `json:"department,omitempty"`
		Grade struct {
			S string `json:"S"`
		} `json:"grade,omitempty"`
		ID struct {
			S string `json:"S"`
		} `json:"id"`
		Type struct {
			S string `json:"S"`
		} `json:"type"`
		Birthdt struct {
			S string `json:"S"`
		} `json:"birthdt,omitempty"`
		Postalcode struct {
			S string `json:"S"`
		} `json:"postalcode,omitempty"`
		Telno struct {
			S string `json:"S"`
		} `json:"telno,omitempty"`
		Name struct {
			S string `json:"S"`
		} `json:"name,omitempty"`
	} `json:"Items"`
	Count            int         `json:"Count"`
	ScannedCount     int         `json:"ScannedCount"`
	ConsumedCapacity interface{} `json:"ConsumedCapacity"`
}

つまり、両方の属性を持った構造体を定義して、取得できなかったものは omitempty すれば良いようだ。
omitempty は、Unmarshal() 時に該当の属性がJSONに入っていなかった場合に、空値にしてくれる。

上記は、あくまでもJSONへのマッピングなので、DynamoDBAttributeValue から Unmarshal() するには、以下のように定義する。

type item struct {
	Id   string `dynamodbav:"id"`
	Type string `dynamodbav:"type"`
	Name string `dynamodbav:"name,omitempty"`
	BirthDt string `dynamodbav:"birthdt,omitempty"`
	TelNo string `dynamodbav:"telno,omitempty"`
	PostalCode string `dynamodbav:"postalcode,omitempty"`
	Department string `dynamodbav:"department,omitempty"`
	Grade string `dynamodbav:"grade,omitempty"`
}

ただし、Query() で複数レコード帰ってくるときは配列なので、さらに以下のように配列化するための構造体を作る。

type items struct {
	Item []item
}

この構造体を活用して、以下のように Query() の結果を Unmarshal() しよう。

var items items

result, err := ddb.Query(&dynamodb.QueryInput{
	TableName: aws.String("table-name"),
	KeyConditionExpression: aws.String("#id = :id"),
	ExpressionAttributeNames: map[string]*string{
		"#id": aws.String("id"),
	},
	ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
		":id": {
			S: aws.String(string(id)),
		},
	},
})

err = dynamodbattribute.UnmarshalListOfMaps(result.Items, &items.Item)

これで、無事、item の配列にマッピングできた状態になる。

さて、これであとは煮るなり焼くなり好きにすれば良いが、せっかくなので、2種類のレコードをお手軽に取得できるようにしてみよう。
※単純に JSON を返せば良いのであれば、さらにここから、別の JSON 定義で marshal() すれば良いが、今回は別の構造体にマッピングしてみる。

type personal struct {
	Id         string
	Name       string
	BirthDt    string
	TelNo      string
	PostalCode string
}

type company struct {
	Id         string
	Department string
	Grade      string
}

type itemsInterface interface {
	getPersonal() personal
	getCompany() company
}

func (items items) getPersonal() personal {
	for _, item := range items.Item {
		if item.Type == "personal" {
			return personal{
				Id:         item.Id,
				Name:       item.Name,
				BirthDt:    item.BirthDt,
				TelNo:      item.TelNo,
				PostalCode: item.PostalCode,
			}
		}
	}
	return personal{}
}

func (items items) getCompany() company {
	for _, item := range items.Item {
		if item.Type == "company" {
			return company{
				Id:         item.Id,
				Department: item.Department,
				Grade:      item.Grade,
			}
		}
	}
	return company{}
}

あとは、Unmarshal() 後に以下のように呼び出せばよい。

	personal := items.getPersonal()
	company  := items.getCompany()

これでもまだ冗長な気がするが、それでも単に GetItem() するよりは、スマートに作れている……かな……?

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?