在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查询来查询数据, 这使得编码更容易,同时也减少了许多潜在的语法错误。
生词
- When it comes to doing
- out of the way
- at this point
- a lot going on
- particular 某一个
- go with
- meta information
- being compared agains
- prevent SQL injection
- out of the box
- potential
- mock data
- make sense to
有疑问加站长微信联系(非本文作者)