kakts-log

programming について調べたことを整理していきます

Go revelでのmongoDBとのコネクション、CRUD処理についてのメモ

概要

Go revelというwebフレームワークで個人のwebアプリを作っている最中にハマった箇所のメモ。

mgoというGoのmongoドライバを使って、mongoDBとのコネクションを保持させ、CRUD処理を1ファイルにまとめて 他のcontroller層から呼び出して使いたかったので その方法をまとめました。 今回、mongoに関しては、特にレプリカセットを組まずに、単一のmongodに対してアクセスさせます。

アプリのファイル構成

revelを使ったアプリのファイル構成は以下のとおりです。 ファイル構成はrevel new app コマンドで自動生成されたままで、特に変えていないです。

app
├── controllers
│   ├── app.go
│   └── session.go
├── init.go
├── models
│   └── user.go
├── routes
│   └── routes.go
├── tmp
│   └── main.go
└── views
    ├── App
    │   ├── Index.html
    │   ├── Test.html
    │   └── register.html
    ├── debug.html
    ├── errors
    │   ├── 404.html
    │   └── 500.html
    ├── flash.html
    ├── footer.html
    └── header.html

今回はapp/controllers/session.goに、mongoDBとのコネクションやCRUDのロジックをまとめます。

goでのmongoDBへのクライアントとしては、mgoというパッケージが一番主流なのでそれを使う。
https://godoc.org/gopkg.in/mgo.v2 mongoのドキュメントサーチのクエリ生成において、mgoにバンドルされているbsonパッケージが使われているので、こちらも利用する https://godoc.org/gopkg.in/mgo.v2/bson

session.goは、ざっと書いてみると以下のような感じになる。
mongodへ接続したときのsession情報を保持しており、毎回 insert search removeなど、mongodへアクセスする際に sessionを確認する。 sessionがない場合は新たにmgo.Dial(dialURL)を呼ぶことでコネクションを貼る。

基本だと思うが、mongodへのアクセスを行う際に毎回コネクションを貼るのは、明らかに良くないし、もともと推奨されていない。
ローカルとかで軽く勉強するときには問題ないが、コネクションを貼るコストが高いので実環境では絶対やってはいけない。

mongoDB側で許容されているコネクションプールの設定は、mongod側の設定で色々変えることができますが、本稿では省きます。

package controllers

import (
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
)

var (
    session *mgo.Session
    err error
)

// dialURL to mongodb instance
const dialURL = "mongodb://192.168.33.10"

// Create new session.
// if session has already exists, reuse it.
func cloneSession(dialURL string) *mgo.Session {
    if session == nil {
        session, err = mgo.Dial(dialURL)
        if err != nil {
            panic(err)
        }
    }
    return session.Clone()
}

// Insert document to specified db.
func InsertEntity(dbName string,
    collection string,
    model interface{}) {

    // Get DB session
    session := cloneSession(dialURL)
    defer session.Close()

    err := session.DB(dbName).C(collection).Insert(model)
    if err != nil {
        panic(err)
    }
}

// Remove document from collection
func RemoveEntry(dbName string,
    collection string,
    model interface{}) {

    session := cloneSession(dialURL)
    defer session.Close()

    err := session.DB(dbName).C(collection).Remove(model)
    if err != nil {
        panic(err)
    }
}

func queryCollection(dbName string,
    collection string,
    query func(c *mgo.Collection) error) error {

    session := cloneSession(dialURL)
    defer session.Close()

    c := session.DB(dbName).C(collection)
    return query(c)
}

func findOne(userId string,
    dbName string,
    collection string,
    ) (results []interface{}, errorCode string) {
    query := bson.M{"userid": userId}
    return Search(query, 0, 1, dbName, collection)
}

func Search(q interface{},
    skip int,
    limit int,
    dbName string,
    collection string,
) (results []interface{}, errorCode string) {

    errorCode = ""
    query := func(c *mgo.Collection) error {
        fn := c.Find(q).Skip(skip).Limit(limit).All(&results)
        if limit < 0 {
            fn = c.Find(q).Skip(skip).All(&results)
        }
        return fn
    }

    search := func() error {
        return queryCollection(dbName, collection, query)
    }

    err := search()
    if err != nil {
        errorCode = "error"
    }
    return
}

まとめ

今回 mongoDBへの接続や 、CRUD処理をすべてsession.goにまとめて、 revelアプリ内でのcontroller層からsession.goを呼び出す形にした。
session生成とCRUDはおそらく別ファイルに分けた方が良いかと思うので、改良の余地がある