使用Golang基于MongoDB构建Restful服务
近来使用Golang来构建Restful服务变得越发流行。我发现有些时候使用MongoDB作为持久存储,这篇文档中, 我会使用Golang和MongoDB来构建一个简单的用户管理为服务。
MongoDB
Section titled “MongoDB”MongoDB因为极简、灵活、高可用以及面向文档的特性得到越来越多市场上的青睐。根据MongoDB之父的解释,它被用来设计组合键值对存储和关系数据库存储的最佳特性。MongoDB在两者之间做妥协,具备了二者的某些有用的功能。
MongoDB的应用场景:Web应用、分析应用的首要数据库,以及弱数据类型的数据,也就是无schema数据。
什么是文档(document)
Section titled “什么是文档(document)”文档就是键值对集合。文档中的键用字符串表示,文档中的值可以是基础的数据类型(字符串、数字、日期等)、数组,也可以是另一个文档。在MongDB内部以二进制JSON格式存储文档数据,也就做BSON。BSON有相似的结构,但专为文档存储而设计。
下面是一个文档数据示例:
{ name: '张三', age: '11', address: '湖北省武汉市光谷一路'}集合(Collection)
Section titled “集合(Collection)”集合是结构或者概念上相似文档的容器。例如,我们会把用户(user)文档存储到(users)集合(collection)中。这里集合的概念就非常类似于关系数据库(RDMS)中表(table)的概念。两者的不同是,集合中的数据是无schema的,是不强制数据结构的,可以是任意的。
查询(Query)
Section titled “查询(Query)”MongoDB不是用SQL,而是使用自己的JSON查询语言。
例如:使用SQL语句查询名叫“张三”的用户
SELECT * from usersWHERE name = '张三'而在MongoDB中,查询的是:
db.users.find({name: 'hello'})MongoDB Golang驱动
Section titled “MongoDB Golang驱动”mgo(发音:mango)是一个Go语言实现的MongoDB驱动程序,这个驱动提供了一个非常简洁易于使用、并经过充分测试API。接下来,在介绍如何通过mgo来实现CRUD(create、react、update、delete)操作之前,将简单介绍下会话管理(session manager)。
session management
Section titled “session management”获取会话
session, err := mgo.Dial("localhsot")单个的会话不允许进行并发处理,所以通常需要使用多个会话。新建一个会话的最快方式是从现有的session中复制一个新的会话:
newSession := session.Copy()defer newSession.Close()新生成的这个会话会使用相同的集群信息和连接池(connection pool)。每一个新建的session必须在生命周期结束时调用Close方法,该会话的资源会视情况而定,是被放回连接池,还是被回收。
mgo需要和bson一同使用,bson使编写查询更加简单。
- 获取集合中所有的文档
c := session.DB("store").C("users")
var users []Usererr := c.Find(bson.M{}).All(&books)- 查询单个文档
c := session.DB("store").C("users")var user Usererr := c.Find(bson.M{"name": "张三"}).One(&user)- 新建文档
c := session.DB("store").C("users")err = c.Insert(&User{"Ale"})- 更新文档
c := session.DB("store").C("users")err = c.Update(bson.M{"name": "张三"}, &book)- 删除文档
c := session.DB("store").C("users")err = c.Remove(bson.M{"name": "张三"})RESTful服务(Golang)
Section titled “RESTful服务(Golang)”Echo是一个高性能、极简的Go语言Web框架。
- 优化的 HTTP 路由。
- 创建可靠并可伸缩的RESTful API。
- 基于标准的HTTP服务器。
- 组 APIs.
- 可扩展的middleware框架。
- Define middleware at root, group or route level.
- 为JSON, XML进行数据绑定,产生负荷。
- 提供便捷的方法来发送各种HTTP相应。
- 对HTTP错误进行集中处理。
- Template rendering with any template engine.
- 定义属于你的日志格式。
- 高度个性化。
- Automatic TLS via Let’s Encrypt
- 支持HTTP/2
具体实现中基于Echo框架来开发,代码在github.com。
package main
import ( "log" "net/http"
"fmt"
"github.com/labstack/echo" "github.com/labstack/echo/middleware" mgo "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson")
type User struct { ID string `json:"id" bson:"_id,omitempty"` Name string `json:"name,omitempty"` Phone string `json:"phone,omitempty"` Age int `json:"age,omitempty"`}
var session *mgo.Session
func init() { s, err := mgo.Dial("localhost") if err != nil { log.Fatal(err) } session = s}
func main() { defer session.Close() ensureIndex(session)
session.SetMode(mgo.Monotonic, true)
e := echo.New() e.Use(middleware.Logger())
e.GET("/users", allUsers) e.GET("/user/:id", getUser) e.PUT("/user", updateUser) e.DELETE("/user/:id", deleteUser) e.POST("/user", saveUser)
e.Logger.Fatal(e.Start(":1424"))}
func ensureIndex(s *mgo.Session) { session := s.Copy() defer session.Close()
c := session.DB("store").C("users")
index := mgo.Index{ Key: []string{"id"}, Unique: true, DropDups: true, Background: true, Sparse: true, } err := c.EnsureIndex(index) if err != nil { panic(err) }}
func saveUser(e echo.Context) error { u := new(User) if err := e.Bind(u); err != nil { return e.JSON(http.StatusBadRequest, err) }
s := session.Copy() defer s.Close()
c := s.DB("store").C("users") err := c.Insert(u) if err != nil { log.Println("Failed insert user", u) if mgo.IsDup(err) { return e.JSON(http.StatusBadRequest, "User with this id alread exists.") }
return e.JSON(http.StatusInternalServerError, "Database error") }
return e.JSON(http.StatusCreated, "SUCCESS")}
func getUser(e echo.Context) error { s := session.Copy() defer s.Clone()
c := s.DB("store").C("users") var u User id := e.Param("id") fmt.Println("userid", id) err := c.Find(bson.M{"_id": id}).One(&u) if err != nil { log.Println("Failed get user", err) return e.JSON(http.StatusNotFound, "Database error") }
return e.JSON(http.StatusOK, u)}
func updateUser(e echo.Context) error { u := new(User) if err := e.Bind(u); err != nil { return e.JSON(http.StatusBadRequest, err) }
s := session.Copy() defer s.Close()
c := s.DB("store").C("users") err := c.Update(bson.M{"_id": u.ID}, &u) if err != nil { switch err { default: log.Fatalln("Failed update user: ", err) return e.JSON(http.StatusInternalServerError, "Database error") case mgo.ErrNotFound: return e.JSON(http.StatusNotFound, "Not found") } } return e.JSON(http.StatusOK, u)}
func deleteUser(e echo.Context) error { s := session.Copy() defer s.Close()
id := e.Param("id")
c := s.DB("store").C("users") err := c.Remove(bson.M{"_id": id}) if err != nil { switch err { default: e.JSON(http.StatusInternalServerError, "Database error") log.Fatalln("Failed delete user: ", err) return err case mgo.ErrNotFound: e.JSON(http.StatusInternalServerError, "User not found") return err } }
return e.JSON(http.StatusOK, "Sucess")}
func allUsers(e echo.Context) error { s := session.Copy() defer s.Close()
c := s.DB("store").C("users")
var users []User err := c.Find(bson.M{}).All(&users) if err != nil { e.JSON(http.StatusInternalServerError, "Database Error") return err }
return e.JSON(http.StatusOK, users)}使用Curl测试服务
Section titled “使用Curl测试服务”curl对于构建和测试RESTful服务来说是一个非常好用的工具,在其他RESTful 服务API的文档中,常常可以看到curl的身影,这里也不例外。
- 请求
curl -X POST -H 'Content-Type: application/json' -d @body.json http://localhsot:1424/user
body.json{ "id": "5", "name": "李四", "age": 11}- 响应
SUCCESS- 请求
curl -X PUT -H 'Content-Type: application/json' -d @body.json http://localhost:1424/user
body.json{ "id": "1", "title": "天一", "age": "-1"}- 响应
{"id":"1","name":"天一","age":-1}查询所有用户
Section titled “查询所有用户”- 请求
使用python -m json.tool将服务返回的json,进行格式化处理。
curl http://localhost:1424/users | python -m json.tool- 响应
[ { "id": "YE/\ufffd\ufffdDj\ufffd\ufffd\u0004\ufffd-", "name": "xiwang" }, { "id": "2", "name": "1" }, { "age": -1, "id": "1", "name": "\u5929\u4e00" }, { "id": "YE7\u001d\ufffdDj\ufffd\ufffd\u0004\ufffd.", "name": "bug" }]查询指定用户
Section titled “查询指定用户”- 请求
curl http://localhost:1424/user/1- 响应
{"id":"1","name":"天一","age":-1}- 请求
curl -X DELETE http://localhost:1424/user/1- 响应
SUCCESS