気楽にDynamoDBを使おう

Merry Christmas! [guregu](https://github.com/guregu)です。[kami](https://github.com/guregu/kami)や[mogi](https://github.com/guregu/mogi)の作者です。
最近開発しているDynamoDBのライブラリーを紹介しようと思います。

## DynamoDBとは

DynamoDBはAmazon Web ServicesのNoSQLデータベースです。安くてスケールしやすいのが特徴です。DynamoDBの基本知識については、@kenichi_nakamuraさんの[この記事を読んでください](http://qiita.com/kenichi_nakamura/items/bc60d4702b5f88d59cfb)。

### aws-sdk-go

AWSの公式がライブラリー。最近やっと1.0になりました！aws-sdk-goでは、DynamoDBへの問い合わせはできるが、かなり[面倒臭いこと](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb#AttributeValue)をやらなきゃいけない。公式のライブラリーはポインターだらけで、データのMarshalとUnmarshalで苦労することがあります。

## Introducing `dynamo`

https://github.com/guregu/dynamo

`dynamo`は、DynamoDBを使いやすくするライブラリーです。シンプルに言うと、`dynamo`は`aws-sdk-go`のラッパーです。MongoDBの[`mgo`](https://labix.org/mgo)にインスパイアされて、よくやる処理をfluentなAPIで、やりやすくしたライブラリーです。`aws-sdk-go`と`dynamo`は簡単に一緒に使えます。

`dynamo`を作ったきっかけは、[グノシー漫画](https://gunosy.co.jp/news/43)のログなどの保存のためです。今でもプロダクションで使われています！

## Examples

Userが何かしらのアクションを記録するデータベースがあるとします。

`Actions`というテーブル名にしましょう。

| Key | Name | Type |
|-----|------|------|
| Hash | UserID | Number |
| Range| Date | String |

数字のUserIDに対する日付のついたイベントを保存します。

イベントはこういうstructで表現できます。


```go
type Event struct {
	UserID    int `dynamo:"User"`
	Date      time.Time
	Action    string         
	Points    int    
	Hidden    bool `dynamo:"-"`         
}
```

`encoding/json`と同じように、struct tagにより、DynamoDBのAttribute Nameを指定できます。`dynamo:"-"`の場合は、その値を保存しないで無視されます。

### セットアップ

`dynamo.New()`にAWSのsessionとconfigを渡します。

```go
import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/guregu/dynamo"
)

func main() {
	db := dynamo.New(session.New(), &aws.Config{
		Region: aws.String("ap-northeast-1"),
	})
	table := db.Table("Actions")
	// ...
}
```

### Put

簡単です！

```go
evt := Event{UserID: 42, Date: time.Now().UTC(), Action: "test"}
err := table.Put(evt).Run()
// check err...
```

条件もつけられます。例えば、これで上書きできなくなります。（新規保存のみ可能）

```go
evt := Event{UserID: 42, Date: time.Now().UTC(), Action: "test"}
err := table.Put(evt).If("attribute_not_exists(ID)").Run() // IDがHash Keyだから
```

条件について詳しく知りたい方は[こちらへどうぞ](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Expressions.SpecifyingConditions.html)。

### Get

`dynamo`では、DynamoDBのQueryとGetItemのAPIは一緒になっています。

#### 一つのアイテム

Hash KeyとRange Keyを指定しないといけません。一つだけの場合は`One`を使います。

```go
var result Event
err := table.Get("UserID", 42).Range("Date", dynamo.Equal, "2015-12-24T...").One(&result)
```

#### 複数のアイテム

UserID = 42のアイテムを全て取得します。

```go
var results []Event
err := table.Get("UserID", 42).All(&results)
```

Filterもかけられます。これで、User 42の12月以前のアクションを取得します。

```go
var results []Event
err := table.Get("UserID", 42).Filter("'Date' < ?", "2015-12-01").All(&results)
```

DateはDynamoDBの[予約語](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/ReservedWords.html)なので、Single Quotes（`''`）をつけないといけません。
Attribute Valuesは、はてな（`?`）をplaceholderとして使う必要があります。

#### Update

Updateで、Attribute単位の変更ができます。
一回の処理で、`Add`や`Update`や`Set`をいくつも実行できます。

例えば、これはatomicなカウンターです。

```go

type Counter struct {
	Table dynamo.Table
	Name  string
}

func (c Counter) Next() (int64, error) {
	type count struct {
		Name  string
		Count int64
	}

	var result count
	err := c.Table.Update("Name", c.Name).Add("Count", 1).Value(&result)
	return result.Count, err
}

// ...
ctr := Counter{table, "UserIDs"}
nextID, err := ctr.Next()
```

`update.Value()`を使うと、変更後の値の取得ができます。

#### Delete

Range KeyのあるテーブルはRange Keyの指定が必要です。

```go
err := table.Delete("UserID", 42).Range("Date", "2015-12-24...").Run()
```

削除した値も取得できます。

```go
var oldValue Event
err := table.Delete("UserID", 42).Range("Date", "2015-12-24...").OldValue(&oldValue)
```

ちなみに、UpdateもOldValueが使えます！

### 終わりに

BatchGetもBatchWriteも、UpdateとGetの色々な機能もあります！ ぜひ[ドキュメント](https://godoc.org/github.com/guregu/dynamo)を見てみてください！

`dynamo`にてIssueとPull Requestが募集中！
https://github.com/guregu/dynamo

DynamoDBのFree Tierは無限なので、DynamoDBは無料運用にとても便利なデータベースだと思います！