アドベントカレンダーのその後、またはGo言語向けAWS SQSライブラリaws-go-sqsを書いた話
2014年の会社のアドベントカレンダーでGoとAWSの現状について書きました。 あれから動きがあったのでアップデートとaws-go-sqsの話を書きます。
goamzとgo-amz
goamzのアップストリームであるCanonicalがリポジトリをlaunchpadからGithubへ移動させたようです。
名前が……。Issue #11を読む限り、 単純なフォーク間のマージはせず、変更を精査し適宜取り込んでいくポリシーのようです。 私もそのほうがよいと思います。
aws-go
小さな修正からXMLでマップを扱うための修正までいくつかPRを出して、すべてマージされました。
AWSではXMLでmapを表現することがよく出てきます。GoでXMLはGoの鬼門のひとつで、 修正前はstruct内のmapが正しくunmarshalできませんでした。
mapをunmarshalするには自分でxml.Unmarshalerインターフェースを実装する必要があります。
aws-goの場合はJSONのAPI定義からUnmarshalerを自動生成すればOKですね。 で、どうやって実装すれば…(Goの標準ライブラリみてもわからん…)
@ono_matope 氏のすばらしい記事が大変参考になりました。
最終的に https://github.com/stripe/aws-go/pull/60 になりました。
aws-go-sqs
crowdmob/goamz/sqsにSQS実装があります。 しかし、APIがプリミティブでもなければ使いやすいわけでもなく、ただ単にその時の都合に合せた感じ になっていて辛い感じです。
バッチ処理もちゃんと動くようにしたかったと考えていたところにaws-goが出てきたので、 それをべースにしたライブラリを書きました。
GoDocのExampleにバッチ処理の例を書いたのでここにも載せておきます。
// MessageAttributes
attrs := map[string]interface{}{
"ATTR1": "STRING!!",
}
// Create messages for batch operation
batchMessages := []queue.BatchMessage{
queue.BatchMessage{
Body: "success",
},
queue.BatchMessage{
Body: "failed",
Options: []option.SendMessageRequest{option.MessageAttributes(attrs)},
},
}
err = q.SendMessageBatch(batchMessages...)
if err != nil {
batchErrors, ok := queue.IsBatchError(err)
if !ok {
log.Fatal(err)
}
for _, e := range batchErrors {
if e.SenderFault {
// Continue if the failure is on the client side.
log.Print(e)
continue
}
// Retry if the failure is on the server side
// You can use e.Index to identify the message
// failedMessage := batchMessages[e.Index]
}
}
バッチエラーの扱いはもうすこしいい方法があるかもしれません。
工夫した点
ラップするためだけにaws-go-sqs側に劣化structを持たせたくありませんでした。 そこで、
- http://commandcenter.blogspot.jp/2014/01/self-referential-functions-and-design.html
- http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
で紹介されている functional options パターンを適用してみました。
つまり、aws-go側のプリミティブなリクエストstructの中身を更新する部分をaws-go-sqsが提供するようにしました。ラップするためだけに便利structを作る必要がなくなりました。
例えば、 https://github.com/nabeken/aws-go-sqs/blob/master/queue/option/option.go#L20 で、 SQSでメッセージを受け取る時に最大メッセージ数やVisibility Timeoutを指定するoptionを用意しています。
ReceiveMessageは functional optionsをvariadicに受け取れるので、以下のように書けます。
messages, err := s.queue.ReceiveMessage(
option.MaxNumberOfMessages(5),
option.UseAllAttribute(),
)
if err != nil {
log.Fatal(err)
}
- リクエストstruct全体を渡す必要はなくなる
- 必要なfunctional optionを渡すだけで済む
- aws-go側に新しいメンバーが追加されてもfunctional optionsを用意すれば動く(aws-go-sqsになければ自分でも書ける)
また、variadicな引数は0個を表現できます。 つまり、
messages, err := s.queue.ReceiveMessage()
if err != nil {
log.Fatal(err)
}
とも書けます。
もう1つはデバッグしやすくする工夫です。 aws-go自体にまだ不安定な要素があるので、正しいリクエストを送っているか?正しくレスポンスをunmarshalしているか?を検証する必要があります。
aws-go-sqsでは http://motemen.hatenablog.com/entry/2014/12/02/go-loghttp を参考に go-loghttp を デバッグ時のみ仕込み、かつ、リクエスト、レスポンスボディも出力するようにして組込みました。
go-loghttpのおかげでめっちゃデバッグが捗りました。
さいごに
aws-go-sqsの紹介とそこでの工夫について書きました。 GoでWorker書くのすごくマッチしていると思うので、 SQSを使うする機会があればぜひお試しください。PR歓迎です。