Golang与Mongo数据库

随着Mongo数据库的使用越来越多,在开发中选择Mongo也并不少见。这是Golang连接Mongo数据库的示例,介绍了开发中应如何写连接Mongo数据库,如何写单元测试,如何mock数据。

驱动包

Golang的Mongo驱动包有两个:

  • 官方包
  • Mgo
    本人正好都使用过这两个包,个人使用体验是官方包偏繁琐些,代码写出来偏mongo命令行风格,不支持ORM功能,所有代码量相对多些。相对而言Mgo支持ORM,写出的代码量少,可维护性好。对于性能方面没有去做过比较,就不做对比了。目前我使用mgo多些,主要考虑因素是编码效率。下面就以mgo为例来介绍

示例

  • 导入包github.com/globalsign/mgogithub.com/globalsign/mgo/bson
  • 连接Mongo mgo.Dial(mongoURL)
  • 指定数据库和Collection session.DB("db name").C("collection")
  • 插入数据 c.Insert(&db.Person{}
  • 查找数据 c.Find(bson.M{}).One()

代码

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
package main

import (
"fmt"
"log"

"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
"github.com/gunsluo/go-example/mgo/db"
)

const (
mongoURL = "mongodb://root:password@127.0.0.1:27017"
)

func main() {
session, err := mgo.Dial(mongoURL)
if err != nil {
panic(err)
}
defer session.Close()

// Optional. Switch the session to a monotonic behavior.
session.SetMode(mgo.Monotonic, true)

c := session.DB("fm").C("people")
err = c.Insert(&db.Person{"Ale", "+55 53 8116 9639"},
&db.Person{"Cla", "+55 53 8402 8510"})
if err != nil {
log.Fatal(err)
}

result := db.Person{}
err = c.Find(bson.M{"name": "Ale"}).One(&result)
if err != nil {
log.Fatal(err)
}

fmt.Println("Phone:", result.Phone)
}

1
2
3
4
5
6
package db

type Person struct {
Name string
Phone string
}

Mock

gomcok是Golang中非常好的mock数据工具,它的原理是基于interface的实现。显然目前版本的mgo不是interface的实现,所以我们需要对mgo实现interface抽象。github上有percona/pmgo项目已经实现了该功能,但它不是最新版本的mgo实现且接口不全。我fork该项目修改了问题实现了pmgo;

查询用户信息

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
package main

import (
"fmt"
"log"

"github.com/globalsign/mgo/bson"
"github.com/percona/pmgo"
)

// User is a user information
type User struct {
ID int `bson:"id"`
Name string `bson:"name"`
}

func main() {
dialer := pmgo.NewDialer()
session, err := dialer.Dial("mongodb://root:password@localhost:27017")
if err != nil {
print(err)
return
}

if err := addUser(session, &User{ID: 1, Name: "jerry"}); err != nil {
log.Printf("error add the user to the db: %s", err.Error())
return
}

user, err := getUser(session, 1)
if err != nil {
log.Printf("error reading the user from the db: %s", err.Error())
return
}
fmt.Printf("User: %+v\n", user)
}

func addUser(session pmgo.SessionManager, user *User) error {
return session.DB("test").C("testc").Insert(user)
}

func getUser(session pmgo.SessionManager, id int) (*User, error) {
var user User
err := session.DB("test").C("testc").Find(bson.M{"id": id}).One(&user)

if err != nil {
return nil, err
}

return &user, nil
}

单元测试代码

  • 使用gomock创建Controller
  • pmgomock模拟mock返回数据
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
package main

import (
"reflect"
"testing"

"github.com/globalsign/mgo/bson"
"github.com/golang/mock/gomock"
"github.com/gunsluo/pmgo/pmgomock"
)

func TestGetUser(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

user := User{
ID: 1,
Name: "Zapp Brannigan",
}

// Mock up a database, session, collection and a query and set
// expected/returned values for each type
query := pmgomock.NewMockQueryManager(ctrl)
query.EXPECT().One(gomock.Any()).SetArg(0, user).Return(nil)

collection := pmgomock.NewMockCollectionManager(ctrl)
collection.EXPECT().Find(bson.M{"id": 1}).Return(query)

database := pmgomock.NewMockDatabaseManager(ctrl)
database.EXPECT().C("testc").Return(collection)

session := pmgomock.NewMockSessionManager(ctrl)
session.EXPECT().DB("test").Return(database)

// Call the function we want to test. It will use the mocked interfaces
readUser, err := getUser(session, 1)

if err != nil {
t.Errorf("getUser returned an error: %s\n", err.Error())
}

if !reflect.DeepEqual(*readUser, user) {
t.Errorf("Users don't match. Got %+v, want %+v\n", readUser, user)
}
}

集成测试

pmgo支持集成测试,测试开始后启动一个真实的mongo服务用于测试。需要指定mongo的服务路径: Server = pmgo.NewDBServer(); Server.SetPath(tempDir)

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
83
84
85
86
87
88
89
90
91
92
93
94
package main

import (
"io/ioutil"
"os"
"reflect"
"testing"

"github.com/globalsign/mgo/bson"
"github.com/golang/mock/gomock"
"github.com/gunsluo/pmgo"
"github.com/gunsluo/pmgo/pmgomock"
)

func TestGetUser(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

user := User{
ID: 1,
Name: "Zapp Brannigan",
}

// Mock up a database, session, collection and a query and set
// expected/returned values for each type
query := pmgomock.NewMockQueryManager(ctrl)
query.EXPECT().One(gomock.Any()).SetArg(0, user).Return(nil)

collection := pmgomock.NewMockCollectionManager(ctrl)
collection.EXPECT().Find(bson.M{"id": 1}).Return(query)

database := pmgomock.NewMockDatabaseManager(ctrl)
database.EXPECT().C("testc").Return(collection)

session := pmgomock.NewMockSessionManager(ctrl)
session.EXPECT().DB("test").Return(database)

// Call the function we want to test. It will use the mocked interfaces
readUser, err := getUser(session, 1)

if err != nil {
t.Errorf("getUser returned an error: %s\n", err.Error())
}

if !reflect.DeepEqual(*readUser, user) {
t.Errorf("Users don't match. Got %+v, want %+v\n", readUser, user)
}
}

var Server pmgo.DBTestServer

func TestIntegration(t *testing.T) {
setup()

readUser, err := getUser(Server.Session(), 1)
if err != nil {
t.Errorf("getUser returned an error: %s\n", err.Error())
}

if !reflect.DeepEqual(*readUser, mockUser()) {
t.Errorf("Users don't match. Got %+v, want %+v\n", readUser, mockUser())
}

tearDown()
}

func setup() {
os.Setenv("CHECK_SESSIONS", "0")
tempDir, err := ioutil.TempDir("", "testing")
if err != nil {
panic(err)
}

Server = pmgo.NewDBServer()
Server.SetPath(tempDir)

session := Server.Session()
// load some fake data into the db
session.DB("test").C("testc").Insert(mockUser())
}

func mockUser() User {
return User{
ID: 1,
Name: "Zapp Brannigan",
}

}

func tearDown() {
Server.Session().Close()
Server.Session().DB("samples").DropDatabase()
Server.Stop()
}