【翻譯】使用Golang+MongoDB構建微服務

原創文章,轉載請註明: 轉載自勤奮的小青蛙
本文連接地址: 【翻譯】使用Golang+MongoDB構建微服務javascript

翻譯來源:http://goinbigdata.com/how-to-build-microservice-with-mongodb-in-golang/html

參考連接:mgo驅動官網java

如今golang成爲熱門的微服務(RESTFul API)開發語言之一。一般,這些微服務系統採用了MongoDB做爲數據庫。在這篇博客裏,咱們將構建一個簡單的圖書管理微服務,採用Golang + MongoDB來完成該微服務。咱們將使用 mgo 來完成Golang與MongoDB的交互。git

MongoDB

MongoDB簡單,高可用性,而且是文檔類型的數據庫,也就是咱們常說的 NoSQL 數據庫。相比 SQL 型的數據庫,它的優點有:github

  • mongodb的文檔對象結構對應了諸多編程語言的數據結構;
  • 組合式的文檔結構減小了昂貴的join鏈接開銷;
  • 文檔動態支持各類各樣數據類型的結構,一個集合中能夠存在各類各樣不一樣數據類型字段的文檔。

什麼是文檔(document)

文檔只是一個由字段和值對組成的數據結構。字段的值可能包括其餘文檔,數組和文檔數組。MongoDB文檔相似於JSON對象,每一個文檔都做爲一個記錄存儲在MongoDB集合中。例如,一本書能夠表示爲如下文檔(json):golang

{
    "isbn":    "0134190440",
    "title":   "The Go Programming Language",
    "authors": ["Alan A. A. Donovan", "Brian W. Kernighan"],
    "price":   "$34.57"
}

什麼是集合(collection)

MongoDB在同一集合中存儲相似的文檔。例如:咱們將在圖書集合中存儲圖書文檔。其實MongoDB中的 collection 相似關係型數據庫中的表結構。不一樣之處在於 collection 不強制對文檔結構進行約束,不過咱們使用的時候儘可能把相同結構的文檔存儲在一個 collection 中。web

查詢(Query)

若是要從MongoDB獲取數據,則必須首先查詢。 Query是一組MongoDB概念,用於指定請求哪些數據的一組過濾器參數。MongoDB使用json和bson(binary json)來編寫查詢。獲取指定isbn的書籍的查詢示例可能以下所示:mongodb

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

響應:

?

1

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構建微服務

相關文章
相關標籤/搜索