Using Couchbase Server In A GoLang Web Application

在Go Web应用中使用couchbase数据库

日期:2016-08-05
作者:Nic Raboy
原文:https://www.thepolyglotdeveloper.com/2016/08/using-couchbase-server-golang-web-application/?utm_source=tuicool&utm_medium=referral
翻译: 一花一世界
时间:2016-09-11
排版:马克飞象(https://maxiang.io

翻译前言
本人根据原文翻译过来,翻译过程中感觉一些常用词语难以用简单的词语表达,深知翻译不易。
如有不当之处,请指教,我会及时更新过来。
另外我也把生词附在文后,以后主要翻译有关GO语言的技术文章,主要以短小的为主。

正文

不久前我写一个文章阐述了怎样用GO语言构建RESTful API, 但是那篇文章里仅用模拟数据而不是真实的数据库。 如果改用真实的数据库在GO语言中该怎样用呢? 用什么数据库, 或者更为重要的,选哪种类型的数据库? 大多数APIs用JSON格式传输, 因此在存数据上也使用相同的格式。这就意味着关系型数据库可能无法使用。 相反,NoSQL数据能很好的适用RESTful APIs。 用JSON的格式来存储数据的数据库中,有一个叫COUCHBASE的开源数据库是比较流行的。

让我们看下在GO语言使用COUCHBASE来开发RESTful web应用。

前面写的文章是本文章例子的基础,因此如果你没有看过, 我强烈推荐你去看下。
如果你不去看,我在这里所说的,你可能感到很迷惑 。

同样,假设在你的机器上已经安装和配置好了GO语言。

用依赖包来创建一个新工程

简单起见,我们将创建一个新工程。 用(MAC和linux)终端或是命令行(windows),执行下面语句

创建一个新的GO工程

mkdir -p $GOPATH/src/github.com/nraboy/cbproject

实际上,我刚才用命令已经创建了上面的目录,如果你的系统中不能生效,则手工创建。

在工程目录中, 有一个中main.go的文件,这是也工作中唯一的一个文件。在开始编码之前, 最好要下载工程中的依赖包。

在命令行或是终端,执行如下操作:
获取扩展的依赖包

go get github.com/satori/go.uuid
go get github.com/couchbase/gocb
go get github.com/gorilla/mux

依赖包中包含一个能产生UUID值的包,它用于产生文档的IDS; Couchbase Go 的SDK,一个用于增加HTTP服务器路由能力的Mux。

给Golang工程配置couchbase服务

在开始编码前先确定一下couchbase的配置是否可用。在这里不讲怎样安装couchbase。如果你在使用Mac, Linux, or Windows 机器, 你可以看一下安装入门的教程。 现在给我们的应用中配置一个可以存储NoSQL文档的桶名。

现在目标是针对应用程序,创建一个名为restful-sample 的couchbase桶名, 这个桶名有一个合适的索引用于执行N1QL查询。

在couchbase的管理员界面中, 选择“Data Buckets”, 然后选择“Create New Data Bucket “。 给这个桶起个名字,并定义一个存储空间的大小。

在couchbase中创建桶

接下来,我给新的桶创建索引。

当谈到创建索引,有不少方法可以实现。 如果couchbase 4.5 或以上,可用Query Workbench。 在Query Workbench执行以下查询:

给N1QL查询创建一个主索引

CREATE PRIMARY INDEX ON restful-sample USING GSI;

使用N1QL至少要有一个索引。 比我这里展示的更为复杂的索引将会使查询更快。

在couchbase中创建主索引

如果你没有安装couchbase4.5或更高的, 另一个方法是需要创建索引。 你可用Couchbase Query Shell (CBQ), 这是couchbase 4.0 或更高之上自带的。对于这些怎样使用的信息可以访问这里。 在Query Workbench中能用的,也可以在Shell中使用。

在此,重点关注实际的应用。

设计RESTful Web 应用

以上的配置都搞定后, 就可以编写代码了。这个应用中有五个基本的端来处理关于“人”的信息。

在工程 $GOPATH/src/github.com/nraboy/cbproject/main.go文件中,增加下面的代码:

$GOPATH/src/github.com/nraboy/cbproject/main.go

package main

import (
    "encoding/json"
    "log"
    "net/http"

    "github.com/couchbase/gocb"
    "github.com/gorilla/mux"
    "github.com/satori/go.uuid"
)

type Person struct {
    ID        string `json:"id,omitempty"`
    Firstname string `json:"firstname,omitempty"`
    Lastname  string `json:"lastname,omitempty"`
    Email     string `json:"email,omitempty"`
}

type N1qlPerson struct {
    Person Person `json:"person"`
}

var bucket *gocb.Bucket

func GetPersonEndpoint(w http.ResponseWriter, req *http.Request) {
    var n1qlParams []interface{}
    query := gocb.NewN1qlQuery("SELECT * FROM `restful-sample` AS person WHERE META(person).id = $1")
    params := mux.Vars(req)
    n1qlParams = append(n1qlParams, params["id"])
    rows, _ := bucket.ExecuteN1qlQuery(query, n1qlParams)
    var row N1qlPerson
    rows.One(&row)
    json.NewEncoder(w).Encode(row.Person)
}

func GetPeopleEndpoint(w http.ResponseWriter, req *http.Request) {
    var person []Person
    query := gocb.NewN1qlQuery("SELECT * FROM `restful-sample` AS person")
    rows, _ := bucket.ExecuteN1qlQuery(query, nil)
    var row N1qlPerson
    for rows.Next(&row) {
        person = append(person, row.Person)
    }
    json.NewEncoder(w).Encode(person)
}

func CreatePersonEndpoint(w http.ResponseWriter, req *http.Request) {
    var person Person
    var n1qlParams []interface{}
    _ = json.NewDecoder(req.Body).Decode(&person)
    query := gocb.NewN1qlQuery("INSERT INTO `restful-sample` (KEY, VALUE) VALUES ($1, {'firstname': $2, 'lastname': $3, 'email': $4})")
    n1qlParams = append(n1qlParams, uuid.NewV4().String())
    n1qlParams = append(n1qlParams, person.Firstname)
    n1qlParams = append(n1qlParams, person.Lastname)
    n1qlParams = append(n1qlParams, person.Email)
    _, err := bucket.ExecuteN1qlQuery(query, n1qlParams)
    if err != nil {
        w.WriteHeader(401)
        w.Write([]byte(err.Error()))
        return
    }
    json.NewEncoder(w).Encode(person)
}

func UpdatePersonEndpoint(w http.ResponseWriter, req *http.Request) {
    var person Person
    var n1qlParams []interface{}
    _ = json.NewDecoder(req.Body).Decode(&person)
    query := gocb.NewN1qlQuery("UPDATE `restful-sample` USE KEYS $1 SET firstname = $2, lastname = $3, email = $4")
    params := mux.Vars(req)
    n1qlParams = append(n1qlParams, params["id"])
    n1qlParams = append(n1qlParams, person.Firstname)
    n1qlParams = append(n1qlParams, person.Lastname)
    n1qlParams = append(n1qlParams, person.Email)
    _, err := bucket.ExecuteN1qlQuery(query, n1qlParams)
    if err != nil {
        w.WriteHeader(401)
        w.Write([]byte(err.Error()))
        return
    }
    json.NewEncoder(w).Encode(person)
}

func DeletePersonEndpoint(w http.ResponseWriter, req *http.Request) {
    var n1qlParams []interface{}
    query := gocb.NewN1qlQuery("DELETE FROM `restful-sample` AS person WHERE META(person).id = $1")
    params := mux.Vars(req)
    n1qlParams = append(n1qlParams, params["id"])
    _, err := bucket.ExecuteN1qlQuery(query, n1qlParams)
    if err != nil {
        w.WriteHeader(401)
        w.Write([]byte(err.Error()))
        return
    }
    json.NewEncoder(w).Encode(&Person{})
}

func main() {
    router := mux.NewRouter()
    cluster, _ := gocb.Connect("couchbase://localhost")
    bucket, _ = cluster.OpenBucket("restful-sample", "")
    router.HandleFunc("/people", GetPeopleEndpoint).Methods("GET")
    router.HandleFunc("/people/{id}", GetPersonEndpoint).Methods("GET")
    router.HandleFunc("/people", CreatePersonEndpoint).Methods("PUT")
    router.HandleFunc("/people/{id}", UpdatePersonEndpoint).Methods("POST")
    router.HandleFunc("/people/{id}", DeletePersonEndpoint).Methods("DELETE")
    log.Fatal(http.ListenAndServe(":12345", router))
}

上面代码中实现了很多功能,拆开来看下是个不错的主意。

首先,我们导入了各种依赖包,这些是前面提到的。用包含在这个工程中的包, 我们可以定义两个结构,一个是代表JSON数据,我们用它来工作。Person结构中包含了Person必要的信息, 它将以JSON的形式存在数据库。当查询数据时, 结果集以父JSON的对象展示,这对N1QLPerson结构是必需的。 查询结果看起来是这样的:

JSON Data from Queries

{
    "person": {
        "id": "1234",
        "firstname": "Nic",
        "lastname": "Raboy",
        "email": "nic@test.com"
    }
}

在这个结构体中要注意omitempty标签的使用。就是说如果有一个属性是空的值,就不会出现在JSON结果中。
在跳到端函数之前, 先执行如下:

$GOPATH/src/github.com/nraboy/cbproject/main.go

var bucket *gocb.Bucket

这个桶变量定义在主函数外面,是个全局变量。当查询数据和打开couchbase桶时,表示使用它。

跳到工程的主函数:
$GOPATH/src/github.com/nraboy/cbproject/main.go

func main() {
    router := mux.NewRouter()
    cluster, _ := gocb.Connect("couchbase://localhost")
    bucket, _ = cluster.OpenBucket("restful-sample", "")
    router.HandleFunc("/people", GetPeopleEndpoint).Methods("GET")
    router.HandleFunc("/people/{id}", GetPersonEndpoint).Methods("GET")
    router.HandleFunc("/people", CreatePersonEndpoint).Methods("PUT")
    router.HandleFunc("/people/{id}", UpdatePersonEndpoint).Methods("POST")
    router.HandleFunc("/people/{id}", DeletePersonEndpoint).Methods("DELETE")
    log.Fatal(http.ListenAndServe(":12345", router))
}

上面的主函数定义了应用的路由和建立一个连接到couchbase簇。 在这种情况下, couchbase簇运行的是我们机器上的本地。 当couchbase簇的连接建立后,就能打开某一个桶。 在这里这个桶称为的restful-sample.

这里有五个不同的路由。 一个是获取所有的文档,一个是获取单一文档,一个是创建文档,一个是更新,最后一个是删除。 注意这些路由中的每一个用了GET, PUT, POST, or DELETE。 我们看下与之相配的每个端函数。

$GOPATH/src/github.com/nraboy/cbproject/main.go

func GetPersonEndpoint(w http.ResponseWriter, req *http.Request) {
    var n1qlParams []interface{}
    query := gocb.NewN1qlQuery("SELECT * FROM `restful-sample` AS person WHERE META(person).id = $1")
    params := mux.Vars(req)
    n1qlParams = append(n1qlParams, params["id"])
    rows, _ := bucket.ExecuteN1qlQuery(query, n1qlParams)
    var row N1qlPerson
    rows.One(&row)
    json.NewEncoder(w).Encode(row.Person)
}

上面的GetPersonEndpoint 函数会从数据库中获取单个人的记录。 元信息中的ID与从路由请求的的id进行比较。这个查询是一个参数化的查询,防止SQL注入。我们返回JSON数据,仅是Person的结构,而不是整个N1plPerson结构。

$GOPATH/src/github.com/nraboy/cbproject/main.go

func GetPeopleEndpoint(w http.ResponseWriter, req *http.Request) {
    var person []Person
    query := gocb.NewN1qlQuery("SELECT * FROM `restful-sample` AS person")
    rows, _ := bucket.ExecuteN1qlQuery(query, nil)
    var row N1qlPerson
    for rows.Next(&row) {
        person = append(person, row.Person)
    }
    json.NewEncoder(w).Encode(person)
}

上面这个GetPeopleEndpoint 函数与前面的相似, 但这次的结果期望是一个切片,而不是一个单一的结构。

create, update, and delete 函数和上面的基本差不多, 使得维护和理解都容易。

至此, 应用程序可以运行了。 可以用cURL, Postman来测试,WEB浏览器没有提供现成的工具测试PUT, POST, or DELETE 。

结论

你刚才看了,通过用couchbase做NoSQL数据库,把前面的RESTful API GO语言的例子做了更为深入的应用。当使用couchbase数据库, 我们用了被称为N1QL,类SQL查询来查询数据, 这使得编码更容易,同时也减少了许多潜在的语法错误。

生词

  1. When it comes to doing
  2. out of the way
  3. at this point
  4. a lot going on
  5. particular 某一个
  6. go with
  7. meta information
  8. being compared agains
  9. prevent SQL injection
  10. out of the box
  11. potential
  12. mock data
  13. make sense to
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值