文档章节

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

mickelfeng
 mickelfeng
发布于 2018/02/08 14:38
字数 1867
阅读 50
收藏 2

原创文章,转载请注明: 转载自勤奋的小青蛙
本文链接地址: 【翻译】使用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"
}

响应:

?

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

本文转载自:http://www.jyguagua.com/?p=3118

共有 人打赏支持
mickelfeng

mickelfeng

粉丝 235
博文 2751
码字总数 596619
作品 0
成都
高级程序员
私信 提问
【西安站】中生代技术十月十城联动

西安,古称“长安”,是举世闻名的世界四大文明古都之一,居中国古都之首,是中国历史上建都时间最长、建都朝代最多、影响力最大的都城,是中华民族的摇篮、中华文明的发祥地、中华文化的代表...

Alaise
2016/09/22
211
1
Chris Richardson微服务翻译:微服务介绍

作者简介:Chris Richardson,世界著名的软件架构师,经典著作《POJOS IN ACTION》的作者,cloudfoundry.com 的创始人 微服务目前正受到大量的关注,成为文章、博客、会议讨论的热点。与此同...

butterfly100
2017/11/01
0
0
专访陈文辉:新技术很重要,但是首先要练好基本功

让我们把时间调回到2003年6月。那一年,承载着“传统J2EE寒冬之后的崭新起点”美好愿景的Spring项目开始立项,并以1.0版本进行推进。 时光荏苒,从Spring Framework 1.0发展到现在的Spring ...

异步社区
2018/08/22
0
0
使用Java构建微服务

原文地址: http://dockone.io/article/804 【编者的话】本文翻译自Dzone Guide to the Java Ecosystem,Dzone是一个关于Java的优秀网站。文中介绍了几种用Java构建微服务的方法,包括Conta...

gsying1474
2016/02/29
0
0
5. 应用服务器与微服务 - JavaEE基础系列

本文是JavaEE基础系列的第五节。 Java EE简介 - JavaEE基础系列 JSR简介 - JavaEE基础系列 什么是JSR参考实现? - JavaEE基础系列 什么是应用服务器? - JavaEE基础系列 Microservices(微服务...

renfufei
2017/09/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

java框架学习日志-13(Mybatis基本概念和简单的例子)

在mybatis初次学习Mybatis的时候,遇到了很多问题,虽然阿里云的视频有教学,但是视频教学所使用的软件和我自己使用的软件不用,我自己用的数据库是oracle数据库,开发环境是idea。而且视频中...

白话
今天
4
0
Java基础:String、StringBuffer和StringBuilder的区别

1 String String:字符串常量,字符串长度不可变。Java中String是immutable(不可变)的。 String类的包含如下定义: /** The value is used for character storage. */private final cha...

watermelon11
今天
2
0
mogodb服务

部署MongoDB 官网: https://www.mongodb.com/download-center/community 创建mongo数据目录 mkdir /data/mongodb 二进制部署 wget -c https://fastdl.mongodb.org/linux/mongodb-linux-x8......

以谁为师
昨天
5
0
大神教你Debian GNU/Linux 9.7 “Stretch” Live和安装镜像开放下载

Debian项目团队于昨天发布了Debian GNU/Linux 9 "Stretch" 的第7个维护版本更新,重点修复了APT软件管理器中存在的安全漏洞。在敦促每位用户尽快升级系统的同时,Debian团队还发布了Debian ...

linux-tao
昨天
4
0
PHP 相关配置

1. php-fpm的pool 编辑php-fpm配置文件php-fpm.con vim /usr/local/php/etc/php-fpm.conf //在[global]部分增加以下内容 include = etc/php-fpm.d/*.conf # 相当与Nginx的虚拟主机文件 “vho......

Yue_Chen
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部