【翻译】使用Golang+MongoDB构建微服务

发布于 2018-07-27 作者 超级苦工 757次 浏览 版块 分享

原创文章,转载请注明: 转载自勤奋的小青蛙
本文链接地址: 【翻译】使用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的参考可以访问如下:

MongoDB官网

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"
}

响应:

201 Created

获取所有图书

演示:

请求:

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构建微服务

文章的脚注信息由WordPress的wp-posturl插件自动生成



|2|left
打赏
微信扫一扫支付
微信logo微信扫一扫,打赏作者吧~
收藏
暂无回复