やる気がストロングZERO

やる気のストロングスタイル

【Golang】sql.Open(), Close()を呼ぶタイミング・場所について考えた

GoでDBアクセスする為のサンプルコードを探すとどこもこんな感じ

func main() {
    # dbコネクション(pool)を取得
    pool, err = sql.Open("driver-name", *dsn)
    if err != nil {
        // This will not be a connection error, but a DSN parse error or
        // another initialization error.
        log.Fatal("unable to use data source name", err)
    }
    defer pool.Close()

    # poolを使ってDBアクセスするコード
    ......
}

Openしたコネクションはアプリ終了時にdefer によってClose()処理されて終わる。理解した。

すべてmain()に処理があったり、数個の関数で構成されているアプリケーションだったら上記で良いけど、 クリーンアーキテクチャ的なレイヤー構造を持ったアプリケーションだったらどうすれば良いのか悩んだ。

レイヤー構造があると、DBアクセスする層ってmainからちょっと距離がある。
(Goで大掛かりなレイヤー構造を作る事自体があまり良くないのかも、、と最近思ったけど棚上げ)

サンプルコードと同じようにやろうとすると以下の感じになるけど違う感。

func main() {
    # dbコネクション(pool)を取得
    pool, err = sql.Open("driver-name", *dsn)
    if err != nil {
        // This will not be a connection error, but a DSN parse error or
        // another initialization error.
        log.Fatal("unable to use data source name", err)
    }
    defer pool.Close()

    someFunc(pool) # 内部でDBアクセスレイヤーまで延々とpoolを持ち回る。。
    ......
}

すべてのレイヤーのメソッドはpoolを受け取る設計にしないといけなくてダルい感じ。
というかそもそも表層はデータストアがDBかどうかを意識したくない。

じゃあ、DBアクセスが必要になったタイミング(DBアクセス層)で初めてOpen()したらいいのかと思ったけど、Closeのタイミングをどうすれば良いのかわからなくなった。 細々Open()Close()すれば一応機能はしそうだけど、それでいいのだろうか?

どうするのが良さそうか考えた。

アプリケーションがDBにアクセスする可能性がある限りCloseしないほうがお得

sqlパッケージのコードを読んでみると、Openで渡ってくる構造体を使ってDBにアクセスする時、一度コネクション接続が確立された後はコネクションが閉じずに保持されていて、コネクションは複数保持されることができる。(これがコネクションプールと呼ばれる)

Close()が実行されると、コネクションプールのコネクションはすべて開放される。

だから、後続の処理がまだDBアクセスする可能性があるなら(というか、そのアプリケーションが起動している間はずっと)Closeしないほうが良い。

アプリケーションが終わる時、死ぬ時にClose()を呼びたい。

アプリケーションが動いている間はずっとコネクションを保持していたいけど、アプリケーションが終了するときにはキチンとClose()したい。
ただし、Goにはデストラクタがない。。
アプリケーションが終わる時はすべてメモリ解放されるだろうからわざわざCloseを呼ばなくても良い気もする(ちょっとよくわからん)。 気分的にはきっちり呼んでおきたい気がする。

シングルトンパターンで一度Openした構造体をアプリケーション内で使い回すようにして、main処理が終了のタイミングでCloseが呼ばれるようにしておく。

こんな感じが良さそうかと思った。(コードはざっくり)

func main() {
    defer DbConnection.Close() # main()が終わる時にClose処理を呼ぶ

    # なんか階層化された構造のプログラムの起動
    layeredStructFunc()
}

# DB接続が必要な時に呼ぶ。シングルトンで一度Openしたコネクションを使い回す。mainが終わる時にClose()が呼ばれる。
type DbConnection struct {
    func GetConnection() {
        # シングルトンでコネクション構造体を返す
        return pool
    }
    func Close() {
        # シングルトンのコネクションを閉じる処理
        pool.Close()
    }
}