原创文章,转载请注明: 转载自勤奋的小青蛙
本文链接地址: 【翻译】使用Golang+MongoDB构建微服务
翻译来源:http://goinbigdata.com/how-to-build-microservice-with-mongodb-in-golang/
参考链接:mgo驱动官网
现在golang成为热门的微服务(RESTFul API)开发语言之一。通常,这些微服务系统采用了MongoDB作为数据库。在这篇博客里,我们将构建一个简单的图书管理微服务,采用Golang + MongoDB来完成该微服务。我们将使用 mgo 来完成Golang与MongoDB的交互。
MongoDB
MongoDB简单,高可用性,并且是文档类型的数据库,也就是我们常说的 NoSQL 数据库。相比 SQL 型的数据库,它的优势有:
- mongodb的文档对象结构对应了诸多编程语言的数据结构;
- 组合式的文档结构减少了昂贵的join连接开销;
- 文档动态支持各种各样数据类型的结构,一个集合中可以存在各种各样不同数据类型字段的文档。
什么是文档(document)
文档只是一个由字段和值对组成的数据结构。字段的值可能包括其他文档,数组和文档数组。MongoDB文档类似于JSON对象,每个文档都作为一个记录存储在MongoDB集合中。例如,一本书可以表示为以下文档(json):
{
"isbn": "0134190440",
"title": "The Go Programming Language",
"authors": ["Alan A. A. Donovan", "Brian W. Kernighan"],
"price": "$34.57"
}
什么是集合(collection)
MongoDB在同一集合中存储类似的文档。例如:我们将在图书集合中存储图书文档。其实MongoDB中的 collection 类似关系型数据库中的表结构。不同之处在于 collection 不强制对文档结构进行约束,不过我们使用的时候尽量把相同结构的文档存储在一个 collection 中。
查询(Query)
如果要从MongoDB获取数据,则必须首先查询。 Query是一组MongoDB概念,用于指定请求哪些数据的一组过滤器参数。MongoDB使用json和bson(binary json)来编写查询。获取指定isbn的书籍的查询示例可能如下所示:
{"isbn": "1234567"}
更多有关MongoDB的参考可以访问如下:
Golang的mongodb驱动
mgo(发音为芒果)是Golang的MongoDB驱动程序。它的API非常简单,遵循标准的Go语法。我们将看到如何使用mgo帮助建立一个微服务器的CRUD(创建,读取,更新,删除)操作,但首先让我们熟悉会话管理。
会话管理
获取会话
session, err := mgo.Dial("localhost")
单个会话不允许并发处理,因此通常需要多个会话。获得另一个会话的最快方法是复制现有的会话。确保使用后关闭它:
anotherSession := session.Copy()
defer anotherSession.Close()
搜索文档
mgo与bson软件包一起使用,这简化了写入查询。
获取所有的文档:
c := session.DB("store").C("books")
var books []Book
err := c.Find(bson.M{}).All(&books)
获取其中一条文档:
c := session.DB("store").C("books")
isbn := ...
var book Book
err := c.Find(bson.M{"isbn": isbn}).One(&book)
创建新文档
c := session.DB("store").C("books")
err = c.Insert(book)
更新文档
c := session.DB("store").C("books")
err = c.Update(bson.M{"isbn": isbn}, &book)
删除文档
c := session.DB("store").C("books")
err := c.Remove(bson.M{"isbn": isbn})
使用MongoDB构建微服务
以下是由Go编写并由MongoDB支持的book store microservice的完整示例。您可以从GitHub下载该示例。
该微服务使用 Goji 来提供路由功能,可以查看这篇博客进行详细了解: How to write RESTful services with Goji
下面是图书管理的微服务核心代码:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"goji.io"
"goji.io/pat"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
func ErrorWithJSON(w http.ResponseWriter, message string, code int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
fmt.Fprintf(w, "{message: %q}", message)
}
func ResponseWithJSON(w http.ResponseWriter, json []byte, code int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
w.Write(json)
}
type Book struct {
ISBN string `json:"isbn"`
Title string `json:"title"`
Authors []string `json:"authors"`
Price string `json:"price"`
}
func main() {
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
ensureIndex(session)
mux := goji.NewMux()
mux.HandleFunc(pat.Get("/books"), allBooks(session))
mux.HandleFunc(pat.Post("/books"), addBook(session))
mux.HandleFunc(pat.Get("/books/:isbn"), bookByISBN(session))
mux.HandleFunc(pat.Put("/books/:isbn"), updateBook(session))
mux.HandleFunc(pat.Delete("/books/:isbn"), deleteBook(session))
http.ListenAndServe("localhost:8080", mux)
}
func ensureIndex(s *mgo.Session) {
session := s.Copy()
defer session.Close()
c := session.DB("store").C("books")
index := mgo.Index{
Key: []string{"isbn"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
}
err := c.EnsureIndex(index)
if err != nil {
panic(err)
}
}
func allBooks(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
c := session.DB("store").C("books")
var books []Book
err := c.Find(bson.M{}).All(&books)
if err != nil {
ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed get all books: ", err)
return
}
respBody, err := json.MarshalIndent(books, "", " ")
if err != nil {
log.Fatal(err)
}
ResponseWithJSON(w, respBody, http.StatusOK)
}
}
func addBook(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
var book Book
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&book)
if err != nil {
ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
return
}
c := session.DB("store").C("books")
err = c.Insert(book)
if err != nil {
if mgo.IsDup(err) {
ErrorWithJSON(w, "Book with this ISBN already exists", http.StatusBadRequest)
return
}
ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed insert book: ", err)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Location", r.URL.Path+"/"+book.ISBN)
w.WriteHeader(http.StatusCreated)
}
}
func bookByISBN(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
isbn := pat.Param(r, "isbn")
c := session.DB("store").C("books")
var book Book
err := c.Find(bson.M{"isbn": isbn}).One(&book)
if err != nil {
ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed find book: ", err)
return
}
if book.ISBN == "" {
ErrorWithJSON(w, "Book not found", http.StatusNotFound)
return
}
respBody, err := json.MarshalIndent(book, "", " ")
if err != nil {
log.Fatal(err)
}
ResponseWithJSON(w, respBody, http.StatusOK)
}
}
func updateBook(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
isbn := pat.Param(r, "isbn")
var book Book
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&book)
if err != nil {
ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
return
}
c := session.DB("store").C("books")
err = c.Update(bson.M{"isbn": isbn}, &book)
if err != nil {
switch err {
default:
ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed update book: ", err)
return
case mgo.ErrNotFound:
ErrorWithJSON(w, "Book not found", http.StatusNotFound)
return
}
}
w.WriteHeader(http.StatusNoContent)
}
}
func deleteBook(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
isbn := pat.Param(r, "isbn")
c := session.DB("store").C("books")
err := c.Remove(bson.M{"isbn": isbn})
if err != nil {
switch err {
default:
ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed delete book: ", err)
return
case mgo.ErrNotFound:
ErrorWithJSON(w, "Book not found", http.StatusNotFound)
return
}
}
w.WriteHeader(http.StatusNoContent)
}
}
使用 Curl 进行测试
curl是构建和测试RESTful微服务的不可或缺的工具。curl也是RESTful API开发文档中经常使用的命令,以提供API调用演示。
添加新书
演示
请求:
curl -X POST -H "Content-Type: application/json" -d @body.json http://localhost:8080/books
body.json:
{
"isbn": "0134190440",
"title": "The Go Programming Language",
"authors": ["Alan A. A. Donovan", "Brian W. Kernighan"],
"price": "$34.57"
}
响应:
1 |
|
获取所有图书
演示:
请求:
curl -H "Content-Type: application/json" http://localhost:8080/books
响应:
200 OK
[
{
"ISBN": "0134190440",
"Title": "The Go Programming Language",
"Authors": [
"Alan A. A. Donovan",
"Brian W. Kernighan"
],
"Price": "$34.57"
},
{
"ISBN": "0321774639",
"Title": "Programming in Go: Creating Applications for the 21st Century (Developer's Library)",
"Authors": [
"Mark Summerfield"
],
"Price": "$31.20"
}
]
获取其中一本书信息
演示:
请求:
curl -H "Content-Type: application/json" http://localhost:8080/books/0134190440
响应:
200 OK
{
"ISBN": "0134190440",
"Title": "The Go Programming Language",
"Authors": [
"Alan A. A. Donovan",
"Brian W. Kernighan"
],
"Price": "$34.57"
}
更新一本书
演示:
请求:
curl -X PUT -H "Content-Type: application/json" -d @body.json http://localhost:8080/books/0134190440
body.json:
{
"isbn": "0134190440",
"title": "The Go Programming Language",
"authors": ["Alan A. A. Donovan", "Brian W. Kernighan"],
"price": "$20.00"
}
响应:
204 No Content
删除一本书
演示:
请求:
curl -X DELETE -H "Content-Type: application/json" -d @body.json http://localhost:8080/books/0134190440
响应:
204 No Content
总结
MongoDB是一个非常受欢迎的后端,用于使用Go编写微服务器。Go(mgo)的MongoDB驱动程序是最常用的,而且非常易于使用。如果您正在构建,测试或记录RESTful服务,请不要忽略curl工具。
原创文章,转载请注明: 转载自勤奋的小青蛙
本文链接地址: 【翻译】使用Golang+MongoDB构建微服务
来源:oschina
链接:https://my.oschina.net/u/105637/blog/1620181