はじめに
こんにちは、TIG DXユニット真野です。この技術ブログの運営もしています。
DynamoDB×Go連載の第3弾目です。今までは AWS SDK Go
やそれをラップしたguregu/dynamo
について説明していましたが、 Go CDK(Go Cloud Development Kit) を用いたDynamoDB操作について説明します。
Go CDKとは?
Go CDKは2018/07 に Google の Go チームが立ち上げたプロジェクトで、Go アプリケーションを各クラウド間でポータブルにすることを目指して、実装されています。
詳しくは Go Cloud連載企画 で全7回に渡って概要から各トピックについて説明しています。特に第1回目 の概要説明から読むことがオススメです。 DynamoDBを含むDocStoreについては第3回目の記事を確認ください。
- Go CDK(公式) https://gocloud.dev/
- Go Cloud連載企画
※名称の揺れについてはGo CDKの方が正式名称ですが、ググラビリティが悪いかなと思い(特にAWS CDKと似ている)、連載版は古い呼び方であるGo Cloudを使わせてもらっていました。
記事の趣旨
ご存じの通り、DynamoDBはKVSと言われているものの、非常に多くの機能が存在します。KVSと名前だけ見ると、PK(Primary Key)に対してGet/Put/Deleteなどの基礎的なCRUD処理や、せいぜいそのBatch操作くらいしかできないイメージがありますよね。
しかし、実際のDynamoDBは遥かに高機能で Global Secondary Index や Local Secondary Index を用いたインデックスアクセスや、SortKeyを利用した範囲指定のスキャン、条件を指定したクエリ、ConditionExpression という用した既存レコードの有無といった条件付きの書き込み機能などが存在します。これらを組み合わせてアトミックカウンターを作る参考記事 も公式からでていたり、条件付きではありますがトランザクションを提供もしています。DynamoDBのクライアントアプリから直接操作することは少いでしょうが、DynamoDB StreamsやGlobal Tablesといった機能も便利ですよね。
一方で、Go CDKはこういったプロダクト固有の機能を隠蔽化・抽象化したAPIを提供するドライバなため、他のデータストアに切り替えたときでも利用可能なAPIしか提供しない設計になっています(はずです)。しかし、それは DynamoDBの機能を100%使い切れないということと同義 なため、直感的にはリスクを避けるべくなるべくAWS公式のSDKを利用しようと考える方も多いのでは無いでしょうか? そういう短絡的な判断を避けるために、 Go CDKが現時点でどこまで機能を網羅しているのか 我々は知る必要があるでしょう。それがわかれば、この案件のユースケースではGo CDKの提供する機能で十分なため、Go CDKを利用しベンダーロックインのリスク低減を図ったり、Go CDKの提供するmemdocstore
といったモック機能を享受しようというより有意義な判断ができるはずです。
というわけで、どの程度DynamoDBの機能がGo CDKから利用できるのか調査していきます。なお、私はDynamoDBのプロフェッショナルではなくイチ開発者ですので色々漏れがあると思います。ぜひTwitterなどでフィードバックを頂ければと思います。
前提
なお、Go CDKは進化が早いため今回対応していないと判断したものも、実は記事の公開後に実装されている可能性があります。差分については release-notes などから適時確認ください。
- 調査日: 2020/02/24
- Go 1.13.4
- Go CDK v0.19.0
調査結果
調査観点は以下としました。それぞれ○が一通りの機能が利用できる、△が一部利用可能、☓は機能提供がされていないということを示しています。
# | Case | AWS SDK | Go CDK | Description |
---|---|---|---|---|
1 | 基本的なCRUD | ○ | ○ | 使いやすいAPIであるし、ハッシュキー、ソートキーともに利用できる |
2 | バッチ処理のCRUD | ○ | ○ | バッチ登録、バッチ検索など様々可能 |
3 | 条件付き書き込み | ○ | △ | 楽観的ロックを用いたデータ操作が可能 |
4 | クエリ | ○ | ○ | WhereやOrderbyやLimitを実行できる |
詳細を説明していきます。
実証コード
それぞれGoのコードベースで記載方法をまとめていきます。
第1回の記事同様 DynamoDB Local を利用してローカル環境を準備します。
https://future-architect.github.io/articles/20200225/
0. 事前準備
SDKのセットアップ
セッション及びDynamoDBを操作するクライアントを生成します。
// Create session. |
オプションの SharedConfigState
に SharedConfigEnable
を設定することで ~/.aws/config
内を参照してくれるようになります。
DynamoDB clientを生成するまでは、公式SDK通りの手順となります。最後にテーブル名、ハッシュキー、ソートキーを指定して OpenCollection
を呼ぶことで、Go CDK経由でドキュメントのCRUD操作を行えるようになります。
この時、ハッシュキー と ソートキー の両方を指定していますが、もしソートキーが無ければ空文字を指定すればOKです。
レコードを表現する構造体の定義
type Item struct { |
docstore
というというタグを利用することで、DynamoDBのキーを指定して構造体とマッピングできます。省略した場合は構造体のキー名がそのまま利用されます
ほぼ、AWS SDKと同じですね。
1. 基本的なCRUD
Create
利用するメソッドは Create
です。
write := Item{MyHashKey: "00001", MyRangeKey: 1, MyText: "some text..."} |
直感的だと思います。
Read
利用するメソッドは Get
です。
read := Item{MyHashKey: "00001", MyRangeKey: 1} |
これも直感的です。
Update
これはやや特殊です。Update
を用いますが、更新する差分を docstore.Mods
というmapに値をもたせます。
updateKey := Item{MyHashKey: "00001", MyRangeKey: 1} |
Go CDKのUpdateは Patch
のような動きをするので注意が必要です。もし、ドキュメント全体を置換したい場合は後述する Replace
を利用します。
Update
は存在しないレコードに対して行うとエラーになります。
notFoundKey := Item{MyHashKey: "99999", MyRangeKey: 1} |
エラーメッセージから推測すると、Go CDKのUpdateはDynamoDBのConditional Expressionsを利用していることがわかりますね。
Replace
項目全体を置き換える場合です。存在しない場合はエラーになります。
replace := Item{MyHashKey: "00001", MyRangeKey: 1, MyText: "replace"} |
簡単ですね。
もし、存在しない場合は Create
, 存在する場合は Replace
をしたい場合は Put
を使うようです。今回はあまりにコードがそのままなので省略します。
Delete
利用するメソッドは Delete
です。
deleteKey := Item{MyHashKey: "00001", MyRangeKey: 1} |
ここまでで一通りのCRUD操作ができることを確認できました。
2. バッチ処理のCRUD
大量データを扱う場合は、1件1件データを登録するのではなくバッチ登録を行いたいケースは多いのでは無いでしょうか?
この場合は Actions
を用います。
// Create |
ActionsはCreateだけではなく、Get/Create/Replace/Put/Update/Delete の6つの操作を混在させて実行も可能です。
参考: https://godoc.org/gocloud.dev/docstore#hdr-Actions
今回は関数をチェーンで登録しましたが、Actionsで ActionList
が取得できるので、もちろんforループと合わせて追加もできます。
3. 条件付き書き込み
Go CDKはドキュメントを読んだ限りは、条件付き書き込みはサポートされていないようです。しかし、Revisionsという機能があり、いわゆる楽観的ロックのような利用用途を公式でサポートされています。
内容は リビジョンフィールド (デフォルト: DocstoreRevision
) を設定すると、このフィールドを用いてバージョンを確認し、Put
、Replace
、Update
、Delete
の操作を安全に行うことができます。
最初に定義した構造体にフィールドを追加します。
type Item struct { |
これを用いて、楽観的ロックを行ってロックが行われているか確認します。どちらも同じドキュメントにUpdateし続けて様子を見てみます。
func main() { |
すると、実行結果が optimistic locking: docstore (code=FailedPrecondition): ConditionalCheckFailedException: The conditional request failed
が発生し、getしてupdateするまでの間に別のgoroutineが同じドキュメントを更新したことが検知できました。
DynamoDBのConditional Expressionsほど万能では無いですが、多くのユースケースはロックを実現したいことが多いと思うので、これで事足りるケースも多いのではないでしょうか?
ちなみに、Revisionフィールドですが、awscliでテーブルを検索すると、UUIDで実現されていました。最初はロック番号のような形式で実装されていると思ったので意外です
もし、更新回数などをアプリケーションとして持ちたい場合は、自分で属性を持つ必要があるのでご注意ください。
>aws dynamodb scan --endpoint-url http://localhost:8000 --table-name MyFirstTable |
4. クエリ
Go Cloud#3 Go CloudのDocStoreを使う の記事で説明されているように、Go CDKはクエリもサポートされています。Where()
、OrderBy()
、Limit()
で、 Whereの演算子は =
, >
, <
, >=
, <=
の5種類です。ほとんどやりたいことはできるのではないでしょうか?
まとめ
- 使ってみた感想としてGo CDKのDocStoreは非常にリッチな機能を持っており、ベーシックなAWS SDKと遜色なく利用できました。Condition Expressionsで複雑な条件を利用しないなど、限られたユースケースであればむしろ生産性が高まるのではないでしょうか
- Go CDKを用いればドライバ切り替えでmemstoreというモック切り替えも可能ですし、テスタビリティとしても有用です
機能上はGo CDKもかなりガンバっていると感じます。DynamoDBアクセスする際の有力な選択肢となりうるのではないでしょうか? ぜひ、私達のチームでも機を見て実戦投入してみてチャレンジしてみたいと思います。
DynamoDB×Go連載企画以外にも多くの連載企画があります。特にGo Cloud連載が今回のテーマに近いです。