七転八起

技術的に気になったことや詰まったことをメモ替わりに書いていきます。

DBの接続・切断の実装の共通化

前回の記事の続きです。今のコードの状態はこちらです。

元のコード

getTasksHandler, createTaskHandler, updateTaskHandler, deleteTaskHandlerの4つのCRUD処理をするハンドラ関数がありますが、これらの関数の最初の部分に下記のようなコードが重複して現れています。

func getTasksHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  // この部分が何度も出現している
    db := getDatabase()
    defer func() {
        if err := db.Close(); err != nil {
            log.Println(err)
        }
    }()

  // DBのデータを用いた処理(省略)
}

ハンドラとロギングの分離」で行ったように関数をチェインして対応したいと思います。

コードの修正

DBと接続した後にデータの処理を行い、それが完了した後にDBとの接続を切断します。このデータの処理の部分が内容によって変わります。なので、この部分を関数として引数に渡して、その関数の前後で接続・切断の処理を行います。言葉で説明するよりもコードを見た方がわかりやすいかと思います。

下記のconnectDatabaseという関数で前述の処理を行います。引数としてdb *pg.DBを引数とする関数を渡します。これは処理の中でdbを必ず使用するからです。それ以外に必要な変数等がある場合は、外側のスコープから渡すようにします。本当は関数を返すようにした方が汎用性が高いような気がしますが、ひとまずこれで良しとします。

type databaseHandler func(*pg.DB)

func connectDatabase(f databaseHandler) {
    db := getDatabase()
    f(db)
    if err := db.Close(); err != nil {
        log.Println(err)
    }
}

4つのハンドラ関数の修正はどれも同じことを行うので、updateTaskHandlerを例にとって修正したコードを見ていきます。updateTaskHandlerの中では、

  1. パスパラメータのidからDBのデータを取得し、task *Taskに格納する
  2. HTTPのBodyで送られてきた更新内容をtaskに格納する
  3. DBのデータを更新する

という処理を行います。これらを*Taskのメソッドにした上で、connectDatabaseの内部で実行します。

func (t *Task) parse(r io.ReadCloser) {
    data, _ := ioutil.ReadAll(r)
    err := json.Unmarshal(data, t)
    if err != nil {
        panic(err)
    }

    if err = r.Close(); err != nil {
        panic(err)
    }
}

func (t *Task) find(db *pg.DB, id uint) {
    if err := db.Model(t).Where("id = ?", id).Select(); err != nil {
        log.Println(err)
    }
}

func (t *Task) update(db *pg.DB) {
    if _, err := db.Model(t).WherePK().Update(); err != nil {
        log.Println(err)
    }
}

func updateTaskHandler(_ http.ResponseWriter, r *http.Request, p httprouter.Params) {
    id, _ := strconv.Atoi(p.ByName("id"))
    task := new(Task)
    connectDatabase(func(db *pg.DB) {
        task.find(db, uint(id))
        task.parse(r.Body)
        task.update(db)
    })
}

最後に

もっといいやり方あるかもしれません。でもこれしか思いつかなかったので、これでよしとします。