Go言語(golang)でTUIアプリを作ろう 第二部入魂編 ( その2 新データベースツールの採用 )

BoltDBを使ってみる。


[1] 新データベースの選択

前回述べたように、当初アプリケーションのデータベースとしてSQLite3をターゲットに進めていたのですが、いささか思惑違いの部分があり、方向転換を余儀なくされてしまいました。database/sqlベースでコーディングを進めていたわけですから、代替のデータベースは、golangで書かれたRDBがあれば最高なのでしょうが、世の中それほど甘くはありません。
今回のアプリケーションは、特に複雑なデータ処理をするわけではないので、RDBでなければ、実装できないというものではありません。それならば、KVS(key/value store)のようなシンプルな構造でも問題はなさそうです。ここは考えを改めて、golangで書かれたKVSを探してみましょう。

そこで、見つけたのが下記のBoltです。

ドキュメントには、

Bolt is a pure Go key/value store inspired by Howard Chu’s LMDB project. The goal of the project is to provide a simple, fast, and reliable database for projects that don’t require a full database server such as Postgres or MySQL.

とあります。Other Projects Using Boltを見ると、相当量の採用事例もあるようですから、今回はこれを使ってみることにしました。


[2] Boltの機能

さて、Boltは、

Since Bolt is meant to be used as such a low-level piece of functionality, simplicity is key. The API will be small and only focus on getting values and setting values. That’s it.

とあるように、APIは単純でわかりやすいものになっています。基本機能を理解するには、ドキュメント内の「Getting Started」を読めば十分でしょう。

Boltでは、データをKey/Value形式で扱うわけですが、そのコレクションをBucketと呼びます。Boltの利用に当たっては、まずはBucketという概念を理解することが重要です。


(1) Bucketの概念

ドキュメントにある例から見ていきましょう。ここでは、“MyBucket"というBucketを作成しています。

db.Update(func(tx *bolt.Tx) error {  
	b, err := tx.CreateBucket([]byte("MyBucket"))  
	if err != nil {  
		return fmt.Errorf("create bucket: %s", err)  
	}  
	return nil  
})  

このBucketに対し、実際のキーとバリューを、Putメソッドで追加していきます。なお、キーはユニークでなければいけません。

db.Update(func(tx *bolt.Tx) error {  
	b := tx.Bucket([]byte("MyBucket"))  
	err := b.Put([]byte("answer"), []byte("42"))  
	return err  
})  

追加されたデータを読み出すのは、Getメソッドです。削除したい場合は、Deleteメソッドを使用します。

db.View(func(tx *bolt.Tx) error {  
	b := tx.Bucket([]byte("MyBucket"))  
	v := b.Get([]byte("answer"))  
	fmt.Printf("The answer is: %s\n", v)  
	return nil  
})  

なお、Bucketからのデータ取り出しには、CursorSeekForeEachといったメソッドも用意されています。ドキュメントの例を見ながら、アプリケーションの要件に合うものを選択すればよいでしょう。

また、Bucketのなかに、Bucketを入れることが可能です。Nested bucketsと呼ぼれるこの機能は、複数のデータを管理するのに有効でしょう。


(2) Transaction

次に理解しておきたいのは、transactionの概念です。
Boltでは、データをハンドリングする間はtransactionを生成しておかないといけません。
先の例で、

db.Update(func(tx *bolt.Tx) error {})  
db.View(func(tx *bolt.Tx) error {})  

と設定されていた部分です。

Boltでは、ある時点で、唯一のread-write transactionと複数のread-only transactionsをサポートします。このtransactionの間のみ、データが有効になるわけです。言うまでもないことですが、更新を伴う処理には、read-write transactionを、読み込みにはread-only transactionsを使用します。上記のメソッド以外に、手動でtransactionを形成することも可能です。

Boltの基本は、これだけ理解しておけば十分だと思います。データベースのオープン、クローズ、データの取り出しなどは、サンプルを見れば直感的にわかります。


(3) Bucketのvalueになにを入れるのか

Bucketを設計するに当たって、まず考えないといけないのは、valueをどのように設定するかでしょう。keyは、ユニークでないといけないことさえ気をつけておけば、自ずと決まってしまいます。また、Autoincrementな連番をキーとして設定することも出来ます。

keyvalueは1対1ですから、valueも基本的には1項目です。ただ、ここに、1フィールドデータだけ入れていては、とてもアプリケーションニーズに対応できません。したがって、valueには複数項目を入れることになります。例えば、単純なデータなら、CSVのような形式でも良いでしょう。しかし、それでは表現力に乏しいですから、複雑な構造を表現できる形式が必要になります。汎用性があって、golangと相性の良いものといえば、JSONしかありません。

これには、具体例が必要なようです。次回は、Bolt上にListDBを定義し、実際にデータを入れてみましょう。