go-sqlrow

この記事はGo2 Advent Calendar 2017 13日目の記事です。 昨日は@kami_zh さんの Goで標準出力をキャプチャするパッケージを書いた でした。 go-sqlrow Go言語で標準パッケージを使用してRDBMSからデータを取ってくるには、以下の様に書きます1。 1 2 3 4 5 6 7 8 9 type Person struct { ID string Name string } db, _ := sql.Open("dn", "dsn") row, _ := db.Query(`SELECT id, name FROM person where id='foo'`) var p Person row.Scan(&p.ID, &p.Name) SQL文を発行するまではいいのですが、最後の行、sql.Row#Scanがくせ者です。 上記の例のように、sql.row#Scanは可変長個のポインタを引数にとり、それらにそれぞれ値をセットします。この例では値の数が2つのため大きな問題ではありませんが、値の数が増えた場合などは非常に面倒です。また、テーブルの構造が変わった場合なども非常に面倒です。 この問題を解決するため、go-sqlrow という小さなパッケージを作りました2。 これは上記のrow.Scanを代わりにやってくれるパッケージです。 機能・使い方は簡単で、先ほどの例を次の様に書き換えます。 1 2 3 4 5 6 7 8 9 type Person struct { ID string Name string } db, _ := sql.Open("dn", "dsn") row, _ := db.Query(`SELECT id, name FROM person where id='foo'`) var p Person sqlrow.Bind(row, &p) 後は内部でrow.Scan相当の処理を行います。 unexportedなフィールドはencoding/json同様、sql.Rowとの対応がとれませんので、注意が必要です。 エラー処理やトランザクションなどは省略 ↩︎ godoc: https://godoc.org/github.com/nasa9084/go-sqlrow  ↩︎ ...

2017-12-13 · nasa9084

Application Specific Context

元ネタは@lestrrat さんの「Abusing type aliases to augment context.Context 」。 golangを用いてHTTPサーバを作る場合、ルーティングを定義するのに以下の様な関数を用います。 1 http.HandleFunc(path string, handler func(w http.ResponseWriter, r *http.Request) もちろん、http.Handleを用いる場合もありますし、gorilla/mux などのライブラリを用いることもあると思います。 ここで重要なのは、func(w http.ResponseWriter, r *http.Request)という引数の方です。 多くの場合、アプリケーションのハンドラ内ではデータベースなどの外部アプリケーション・ミドルウェアを用いることになります。 しかし、golangのHTTPアプリケーションでは、ハンドラ関数の形式がfunc (w http.ResponseWriter, r *http.Request)と決まっています。引数の追加はできないため、引数以外の方法でDB接続情報などを渡す必要があります。 これまで、golangでWebアプリケーション開発を行う場合によく用いられていたデータベースコネクションの保持方法は、dbパッケージを作成し、そこにパッケージ変数として持つ方法かと思います。が、グローバルな変数はできるだけ持ちたくない ですよね。 そこで、Go 1.8から追加されたcontext を使うことができます。http.Request にはcontext.Contextが入っていて、Request.Context() でget、Request.WithContext() でsetできます。 context.Contextに値を持たせる方法で最初に思いつくのはContext.WithValue() を用いる方法ですが、これは値を取得する度にtype assertionをする必要があり、あまりよくありません 。 これを解消するため、自分で型を定義するのがよいでしょう。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package context // internal context subpackage import ( "context" "errors" ) type withSomethingContext struct { context.Context something *Something } func WithSomething(ctx context.Context, something *Something) context.Context { return &withSomethingContext{ Context: ctx, something: something, } } func Something(ctx context.Context) (*Something, error) { if sctx, ok := ctx.(*withSomethingContext); ok { if sctx.something != nil { return sctx.something, nil } } return nil, errors.New(`no asscosiated something`) } このように定義をすることで、毎回type assertionをする必要もなくなり、すっきりします。 ...

2017-11-21 · nasa9084

Functional Option Pattern

Fuctional Option PatternはGo言語において構造体の初期化時にオプション引数を与えるためのデザインパターンで、元ネタはRob Pike氏のSelf-referential functions and the design of options 、Dave Cheney氏のFunctional options for frendly APIS です。 Go言語には他の言語でオプション引数やキーワード引数と呼ばれる、省略可能な引数が存在しません。 通常は大きな問題は無いのですが、しかし、構造体の初期化時には、省略可能引数がほしくなる場合もあります。 Dave Cheney氏の記事にもある例を見てみましょう。 例 1 2 3 4 5 6 7 8 9 10 11 12 13 type Server struct { listener net.Listener } func NewServer(addr string) (*Server, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err } srv := Server{listener: l} go srv.run return &srv, nil } よくある構造体の初期化関数です。 初期化が上手くいけば、ポインタとnilを、上手くいかなければnilとエラーを返す形になっています。 ここで、Serverになにがしかの拡張を加えることを考えます。たとえばタイムアウトや、TLS対応等です。 しかしこれらは指定する必要が無い場合もあります。 Go言語を用いたアプローチですぐに思いつくのは、オプションの組み合わせの数だけ初期化関数を作成することですね。(たとえば、With...というサフィックスを使って) しかしこれは、オプションの数が増えると、作成しなければならない関数の数が膨大になっていきます。 保守の観点から見てもこれは余りうれしくありません。 Config構造体を用いる そこでよく用いられるのが、設定を保持する構造体を用いる方法です。 例としては、以下の様にします。 1 2 3 4 5 6 7 8 type Config struct { Timeout time.Duration Cert *tls.Cert } func NewServer(addr string, config Config) { // ... } これも良く用いられているパターンです。 しかし、オプションを一切与えない場合のパターンを考えてみましょう。 ...

2017-09-26 · nasa9084

go-redis, redigo, boltのベンチマークを取ってみた

tl;dr データベースに接続済みの状態からstringで値をセット・ゲットするベンチマーク BoltのGetがめちゃめちゃ速い go-redisよりはredigoの方が速い Boltのセットがメモリアロケーションすごく多い result 1 2 3 4 5 6 7 8 9 $ go test -bench . BenchmarkRedisSet-4 10000 246527 ns/op 249 B/op 9 allocs/op BenchmarkRedisGet-4 5000 231569 ns/op 225 B/op 9 allocs/op BenchmarkRedigoSet-4 5000 204545 ns/op 70 B/op 4 allocs/op BenchmarkRedigoGet-4 5000 209392 ns/op 80 B/op 6 allocs/op BenchmarkBoltSet-4 10000 166142 ns/op 34287 B/op 57 allocs/op BenchmarkBoltGet-4 1000000 1140 ns/op 488 B/op 8 allocs/op PASS ok practices/redis-bolt-benchmark 8.705s source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 package rbbench_test import ( "testing" "github.com/boltdb/bolt" redigo "github.com/garyburd/redigo/redis" redis "github.com/go-redis/redis" ) var redisOpts = &redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, } func BenchmarkRedisSet(b *testing.B) { client := redis.NewClient(redisOpts) defer client.Close() b.ResetTimer() for i := 0; i < b.N; i++ { client.Set("key"+string(i), "value", 0).Err() } } func BenchmarkRedisGet(b *testing.B) { client := redis.NewClient(redisOpts) defer client.Close() b.ResetTimer() for i := 0; i < b.N; i++ { client.Get("key" + string(i)).Val() } } func BenchmarkRedigoSet(b *testing.B) { conn, _ := redigo.Dial("tcp", "localhost:6379") defer conn.Close() b.ResetTimer() for i := 0; i < b.N; i++ { conn.Do("SET", "key"+string(i), "value") } } func BenchmarkRedigoGet(b *testing.B) { conn, _ := redigo.Dial("tcp", "localhost:6379") defer conn.Close() b.ResetTimer() for i := 0; i < b.N; i++ { redigo.String(conn.Do("GET", "key"+string(i))) } } func BenchmarkBoltSet(b *testing.B) { db, _ := bolt.Open("bolt.db", 0600, nil) defer db.Close() b.ResetTimer() for i := 0; i < b.N; i++ { db.Update(func(tx *bolt.Tx) error { b, _ := tx.CreateBucketIfNotExists([]byte("bucket")) b.Put([]byte("key"+string(i)), []byte("value")) return nil }) } } func BenchmarkBoltGet(b *testing.B) { db, _ := bolt.Open("bolt.db", 0600, nil) defer db.Close() b.ResetTimer() for i := 0; i < b.N; i++ { db.View(func(tx *bolt.Tx) error { _ = string(tx.Bucket([]byte("bucket")).Get([]byte("key" + string(i)))) return nil }) } }

2017-09-07 · nasa9084

io.Writer.Write()とfmt.Fprintf()のBenchmark

tl;dr 基本的にio.Writer.Write()を使用するのが高速なようです。 result 1 2 3 4 5 6 $ go test -bench . -benchmem BenchmarkWrite-4 30000000 48.7 ns/op 16 B/op 1 allocs/op BenchmarkWriteWithBytes-4 500000000 3.95 ns/op 0 B/op 0 allocs/op BenchmarkFprintf-4 20000000 91.5 ns/op 0 B/op 0 allocs/op BenchmarkWriteTo-4 100000000 10.0 ns/op 0 B/op 0 allocs/op BenchmarkWriteWithBufferBytes-4 300000000 4.31 ns/op 0 B/op 0 allocs/op source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package main_test import ( "bytes" "fmt" "io" "net/http" "testing" ) var s = "Hello, my world" var bs = []byte(s) var buf = bytes.Buffer{} type NullWriter struct{} func (w *NullWriter) Write(b []byte) (int, error) { return len(b), nil } func BenchmarkWrite(b *testing.B) { var w io.Writer = &NullWriter{} b.ResetTimer() for i := 0; i < b.N; i++ { w.Write([]byte(s)) } } func BenchmarkWriteWithBytes(b *testing.B) { var w io.Writer = &NullWriter{} b.ResetTimer() for i := 0; i < b.N; i++ { w.Write(bs) } } func BenchmarkFprintf(b *testing.B) { var w io.Writer = &NullWriter{} b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(w, s) } } func BenchmarkWriteTo(b *testing.B) { var w io.Writer = &NullWriter{} b.ResetTimer() for i := 0; i < b.N; i++ { buf.WriteTo(w) } } func BenchmarkWriteWithBufferBytes(b *testing.B) { var w io.Writer = &NullWriter{} b.ResetTimer() for i := 0; i < b.N; i++ { w.Write(buf.Bytes()) } }

2017-09-02 · nasa9084

sync.WaitGroup

Goroutineを使用して複数の処理を並列で実行、すべてが終わったら次の処理に進みたいという場合があると思います。 Goroutineでデータのリストを作るという処理を考えます。 データの順番は関係なく、すべてのGoroutineでのデータがそろったら次の処理をしたいという設定です。 この場合、単純に考えると以下のようなコードになりますが、以下のコードではデータがそろう前に次の処理が行われます。 1 2 3 4 5 6 7 datalist := []string{} for i := 0; i < 10; i++ { go func() { // something w/datalist } } fmt.Println("next step") このような場合に、sync.WaitGroupを使用します。 sync.WaitGroupは基本的にはただのカウンタですが、カウンタがゼロになるまで処理を待つことができます。 言葉で説明してもわかりにくいと思いますので、ソースコードを見てみましょう。 1 2 3 4 5 6 7 8 9 10 11 12 datalist := []string{} wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) // Goroutineの数だけカウンタを増やす go func() { // something w/datalist wg.Done() // カウンタを減らす } } wg.Wait() // カウンタが0になるまでブロックする fmt.Println("next step") 上記の様にすることで、for文の部分ではGoroutineで並列に実行しつつ、次の処理は並列実行部分が終わってからという動作をさせることができます。 ポイントはカウンタを増やす部分です。 Goroutine内ではなく、外側でAdd(1)します。 Goroutine内でAdd(1)してしまうと、wg.Wait()に到達した時点でGoroutineがまだどれも実行されておらず、次の処理に進んでしまう可能性があるので、必ずGoroutineの外側で実行することが肝要です。

2017-09-01 · nasa9084

Go 1.9 is released

先日2017年8月24日にGo 1.9がリリースされました。 ダウンロードページ からダウンロード可能です。 最大の変更点はGo1.9rc1 is released! でもお伝えしたように、Type Alias でしょう。 型名に対して別名をつけることができる機能です。 また、そのほかにも多くの変更が加えられています。 リリースノートはこちら です。 以下では、いくつか変更点を見ていきます。 Ports Go 1.9から新しく2つのOSと1つのプロセッサアーキテクチャがサポートされています。 POWER8 IBMのPOWER8プロセッサがサポートされています。 GOARCH=ppc64またはGOARCH=ppc64leで使用することができます。 FreeBSD FreeBSD 9.3で動作します。。。が、すでにFreeBSDはサポート切れです。(なんでや・・・・) Go 1.10からはFreeBSD 10.3+で動作する様になる予定です。 OpenBSD 6.0 OpenBSD 6.0がサポートされました。 かわりに、Go 1.9ではOpenBSD5.9をサポートしていません。 Parallel Compilation パッケージの関数を並列コンパイルできるようになりました。 並列コンパイルはデフォルトでONになっており、無効化するには環境変数でGO19CONCURRENTCOMPILATIONを0に設定します。 Vendor matching with ./… これまで、./...というディレクトリ表現はvendorディレクトリも含んでいました。しかし、go testの場合などvendorディレクトリは含まれない方がうれしい場合も多く、実際glide nvなどでvendorディレクトリを含まないディレクトリマッチングが実装されていました。 go1.9からは./...にはvendorディレクトリが含まれないようになり、vendorディレクトリにマッチさせたい場合は./vendor/...と書く必要があります。 Moved GOROOT Go 1.9から、GOROOTが移動となりました。 起動されたパスから自動でGOROOTを探索します。 これにより、Goのインストールパスが違う場所に移動しても、Goのツール類は継続して使える用になりました。 Compiler Toolchain 複素数の割り算がC99準拠となりました。 Doc 長い引数リストは省略されます。 これはgo docで生成されるコードの可読性向上のためです。 また、構造体フィールドのドキュメンテーションがサポートされました。go doc http.Client.Jarなどでどうなったのか確認することができます。 env go env -jsonフラグによりJSON出力することができるようになりました。 Test go testコマンドに-listフラグが追加されました。 これに正規表現で引数を与えることで、テスト名・ベンチマーク名・Exampleテスト名を調べることができます。 Vet vetコマンドがより強化されました。 ...

2017-08-28 · nasa9084

Go1.9rc1 is released!

Go1.9rc1がリリースされました! そこで、Go1.9のリリースノートをさらっと見てみようと思います。 (まだrc1なので、今後変更される場合があります。ご注意を) 全部見ていくと、結構な量になりそうなので、すぐに影響のありそうな部分だけ、軽く見ていきましょう。 type alias Go1.9ではType Aliasというものが導入されます。 これはその名の通り、型に別名をつけられるというもの。 言葉で説明するより、コードを見た方が早いと思いますので、コードを用意しました。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package main import ( "fmt" "log" ) type T1 struct { ID string Name string } type T2 = T1 func main() { t := T1{ ID: "id", Name: "Taro", } log.Println("call t1.Call()") fmt.Println(t.Call()) log.Println("call t2.Call2()") fmt.Println(t.Call2()) return 0 } func (t *T1) Call() string { return "hello, " + t.Name } func (t *T2) Call2() string { return "hi, " + t.ID } go1.8以前ではsyntax errorになるこのコードですが、go1.9では正常に動作し、以下のような出力をします。 ...

2017-07-26 · nasa9084

golang: net/httpでBASIC認証

golangでベーシック認証するのはどうしたら良いのかなー。って思ってたら、net/httpでhandlerに渡されるhttp.RequestにBasicAuth()というメソッドが生えてました。 これはBASIC認証用のユーザ名、パスワード、ヘッダ解析のフラグという値を返してくれます。 なので、 1 2 3 4 5 6 7 8 9 func handler(w http.ResponseWriter, r *http.Request) { username, password, ok := r.BasicAuth() if !ok { return } if username == "hogehogeuser" && password == "fugafugapasswd" { // something } } とすることで認証することができます。簡単、簡単。 なお残念ながらダイジェスト認証はサポートされていない様子。

2017-05-02 · nasa9084