新聞中心
??想了解更多關(guān)于開(kāi)源的內(nèi)容,請(qǐng)?jiān)L問(wèn):??

?? 開(kāi)源基礎(chǔ)軟件社區(qū)??
??https://ost.??
一、前言
本文將使用功能強(qiáng)大的 Gorilla Mux、GORM 和 CockroachDB 編寫(xiě)可維護(hù) RESTful API。利用到的 Go 語(yǔ)言相關(guān)技術(shù)有:
- ??Gorilla/Mux??:功能強(qiáng)大的 URL 路由器和調(diào)度組件。
- ??CockroachDB??:開(kāi)源,云原生分布式 SQL 數(shù)據(jù)庫(kù)系統(tǒng)。
- ??GORM??:神奇的 ORM 庫(kù)。
運(yùn)行環(huán)境:
- Ubuntu 18.04.6 LTS。
- 開(kāi)發(fā)工具:Visual Studio Code。
- 測(cè)試工具:??APIfox??:Apifox = Postman + Swagger + Mock + JMeter。
二、CockroachDB 介紹
CockrocreDB 是一個(gè)云原生分布式 SQL 數(shù)據(jù)庫(kù),旨在構(gòu)建,擴(kuò)展和管理現(xiàn)代數(shù)據(jù)密集型應(yīng)用程序。
官方介紹翻譯如下:
CockroachDB 是一個(gè)分布式關(guān)系型數(shù)據(jù)庫(kù),建立在事務(wù)性和強(qiáng)一致性鍵值存儲(chǔ)之上,具有水平擴(kuò)展、高可用、支持強(qiáng)一致性 ACID 事務(wù)隔離級(jí)別的特點(diǎn),并提供統(tǒng)一的 SQL API 用于結(jié)構(gòu)化、操作和查詢數(shù)據(jù)。
簡(jiǎn)單來(lái)說(shuō),CockroachDB 充分利用了上一代數(shù)據(jù)庫(kù)系統(tǒng)的優(yōu)勢(shì)、SQL 的強(qiáng)大能力以及關(guān)系數(shù)據(jù)模型和現(xiàn)代云原生法則,成為了一個(gè)與其他基于 SQL 的事務(wù)型數(shù)據(jù)庫(kù)廣泛兼容的數(shù)據(jù)庫(kù)系統(tǒng)。
CockroachDB 對(duì) Go 也有很好的支持,比如 pgx、pg、GORM 和 upper/db。
1、下載安裝
針對(duì)最新版的 CockroachDB Linux 下載,點(diǎn)??此處?? 可以看官方的教程寫(xiě)的很詳細(xì),這里本文將跟著照做一次。(選擇其他系統(tǒng),同理)。
- 下載適用于 Linux 的 CockroachDB 包和其支持庫(kù),并將二進(jìn)制文件復(fù)制到您的 PATH 中,以便您可以從任何 shell 執(zhí)行 cockroach 命令:
curl https://binaries.cockroachdb.com/cockroach-v22.1.3.linux-amd64.tgz | tar -xz && sudo cp -i cockroach-v22.1.3.linux-amd64/cockroach /usr/local/bin/
如圖所示:
- CockroachDB 使用??GEOS?? 庫(kù)的定制版本。默認(rèn)情況下,CockroachDB 在/usr/local/lib/cockroach 或 CockroachDB 二進(jìn)制文件當(dāng)前目錄的lib 子目錄中查找外部鏈接庫(kù)。
所以,您可以將二進(jìn)制文件復(fù)制到您的路徑中,以便您可以從任何目錄執(zhí)行 Cockroach 命令:
sudo cp -R cockroach-v22.1.3.linux-amd64/* /usr/local/bin
- 確保剛剛安裝的 cockroach 已經(jīng)成功安裝:
which cockroach
- 啟動(dòng)使用啟動(dòng)臨時(shí)本地內(nèi)存集群,使用cockroach demo 命令:
如果能看到上圖,說(shuō)明 CockroachDB 安裝成功,恭喜~
2、使用本地集群
官方提供兩種方式利用 GORM 使用 CockroachDB:一種是 CockroachDB Serverless(測(cè)試階段),另一種是使用本地集群。
第一種方式需要??注冊(cè)??一個(gè) CockroachDB 云賬號(hào),然后按照官方提示創(chuàng)建鏈接。
本文將使用第二種方式:
- 運(yùn)行cockroach start-single-node 命令:
cockroach start-single-node --advertise-addr 'localhost' --insecure
通過(guò)增加 --insecure 參數(shù)啟動(dòng)了一個(gè)不安全的單節(jié)點(diǎn)群集。
- 記錄下 SQL Shell 中歡迎文本中以下連接信息:
CockroachDB node starting at 2022-07-14 13:21:35.179273246 +0000 UTC (took 1.6s)
build: CCL v22.1.3 @ 2022/07/11 14:04:38 (go1.17.11)
webui: http://localhost:8080
sql: postgresql://root@localhost:26257/defaultdb?sslmode=disable
sql (JDBC): jdbc:postgresql://localhost:26257/defaultdb?sslmode=disable&user=root
RPC client flags: cockroach--host=localhost:26257 --insecure
logs: /home/yuzhou/cockroach-data/logs
temp dir: /home/yuzhou/cockroach-data/cockroach-temp2801178939
external I/O path: /home/yuzhou/cockroach-data/extern
store[0]: path=/home/yuzhou/cockroach-data
storage engine: pebble
clusterID: 43919fea-32cd-48f9-b585-713731d43ee9
status: restarted pre-existing node
nodeID: 1
其中,postgresql://root@localhost:26257/defaultdb?sslmode=disable ,通過(guò) SQL 連接字符串連接我們的 CockroachDB 集群中。
PS:這條信息需要加入到后續(xù)的數(shù)據(jù)庫(kù)連接 datebase.go 文件中,請(qǐng)注意。
三、項(xiàng)目介紹
1、項(xiàng)目功能
本文創(chuàng)建一個(gè)應(yīng)用是一個(gè)簡(jiǎn)單的 RESTful API 服務(wù)器:線上書(shū)店,它將公開(kāi)書(shū)籍的訪問(wèn)和操作,主要包括如下功能:
- 創(chuàng)建一本書(shū)籍
- 獲取書(shū)籍清單
- 獲取一本書(shū)籍信息
- 更新已有書(shū)籍信息
- 刪除一本書(shū)籍
2、API 接口規(guī)范
為了實(shí)現(xiàn)上述的功能,我們的應(yīng)用需要提供給外界如下的 API 接口規(guī)范:
- 創(chuàng)建一本書(shū)籍:通過(guò)/book 響應(yīng)有效的 POST 請(qǐng)求。
- 獲取書(shū)籍清單:通過(guò)/books 響應(yīng)有效的 GET 請(qǐng)求。
- 獲取一本書(shū)籍:通過(guò)/book/{id} 響應(yīng)對(duì)應(yīng)的 GET 請(qǐng)求。
- 更新一本書(shū)籍:通過(guò)/book/{id} 響應(yīng)對(duì)應(yīng)的 PUT 請(qǐng)求。
- 刪除一本書(shū)籍:通過(guò)/book/{id} 響應(yīng)對(duì)應(yīng)的 DELETE 請(qǐng)求。
通過(guò) {id} 能夠有效確定某本書(shū)籍。
豆瓣圖書(shū)鏈接中 https://book.douban.com/subject/26859123/ 的 26859123 就能對(duì)應(yīng)到 《Go程序設(shè)計(jì)語(yǔ)言(英文版)》這本書(shū)。
3、項(xiàng)目結(jié)構(gòu)
本文將創(chuàng)建一個(gè)非常簡(jiǎn)單的應(yīng)用程序結(jié)構(gòu),這是本文使用的 mybook 應(yīng)用的項(xiàng)目結(jié)構(gòu):
.
├── go.mod
├── go.sum
├── handlers.go
├── main.go
└── model
├── database.go
└── model.go
4、安裝項(xiàng)目依賴
安裝 go get -u github.com/gorilla/mux,如下。
安裝 go get -u gorm.io/gorm。
安裝 go get -u gorm.io/driver/postgres。
四、應(yīng)用功能設(shè)計(jì)
首先創(chuàng)建一個(gè) mybook 的目錄(應(yīng)用文件夾),然后使用 go mod init mybook。
接著在其中創(chuàng)建我們的 model 文件夾,接下來(lái),在 model 文件夾下新建一個(gè) model.go 文件。
1、model.go
我們需要設(shè)計(jì)出 book 模型,這里即將使用 GORM 進(jìn)行創(chuàng)建,編寫(xiě)我們的 model.go 函數(shù):
package model
import (
"gorm.io/gorm"
)
type Book struct {
gorm.Model
ID string `json:"id"`
Name string `json:"name"`
Author string `json:"author"`
Description string `json:"description"`
Price float64 `json:"price"`
Category string `json:"category"`
}
如上所示,書(shū)籍的結(jié)構(gòu)體中進(jìn)行簡(jiǎn)單設(shè)計(jì)包含 id(ID)、書(shū)名(Name)、作者(Author)、書(shū)籍描述(Description)、價(jià)格(Price)和類別(Category)。
2、database.go
該程序?qū)?chuàng)建數(shù)據(jù)庫(kù)連接,需要使用到 postgresql://root@localhost:26257/defaultdb?sslmode=disable 傳入到 gorm.Open() 的連接參數(shù)中,代碼如下:
package model
import (
"log"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func SetupDB() (*gorm.DB, error) {
dsn := "postgresql://root@localhost:26257/defaultdb?sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("can't connect to database", err)
}
var now time.Time
db.Raw("SELECT NOW()").Scan(&now)
log.Println(now)
if err = db.AutoMigrate(&Book{}); err != nil {
log.Println(err)
}
return db, err
}
3、handler.go
回到 mybook 文件夾下,創(chuàng)建 handler.go :
package main
import (
"encoding/json"
"net/http"
"mybook/model"
"github.com/gorilla/mux"
"gorm.io/gorm"
)
type Server struct {
db *gorm.DB
}
type UpdateBook struct {
Price float64 `json:"price"`
Description string `json:"decription"`
Category string `json:"category"`
}
func NewServer(db *gorm.DB) *Server {
return &Server{db: db}
}
func (s *Server) RegisterRouter(router *mux.Router) {
router.HandleFunc("/books", s.getBooks)
router.HandleFunc("/book/{id}", s.getBook).Methods("GET")
router.HandleFunc("/book", s.createBook).Methods("POST")
router.HandleFunc("/book/{id}", s.updateBook).Methods("PUT")
router.HandleFunc("/book/{id}", s.deleteBook).Methods("DELETE")
}
func (s *Server) getBooks(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
var books []model.Book
if err := s.db.Find(&books).Error; err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(books)
}
func (s *Server) createBook(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
var book model.Book
if err := json.NewDecoder(r.Body).Decode(&book); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
newBook := model.Book{Price: book.Price, Description: book.Description, Category: book.Category}
if err := s.db.Create(newBook).Error; err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(newBook)
}
func (s *Server) getBook(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
var book model.Book
vars := mux.Vars(r)
id := vars["id"]
if err := s.db.Where("id = ?", id).First(&book).Error; err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(book)
}
func (s *Server) updateBook(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
var updateBook UpdateBook
var book model.Book
vars := mux.Vars(r)
id := vars["id"]
if err := json.NewDecoder(r.Body).Decode(&updateBook); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := s.db.Where("id = ?", id).First(&book).Error; err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if err := s.db.Model(&book).Updates(&model.Book{
Price: updateBook.Price,
Description: updateBook.Description,
Category: updateBook.Category}).Error; err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(book)
}
func (s *Server) deleteBook(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
var book model.Book
vars := mux.Vars(r)
id := vars["id"]
if err := s.db.Where("id = ?", id).First(&book).Error; err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if err := s.db.Delete(&book).Error; err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode("Book Deleted Successfully!")
}
4、main.go
在 main.go 中,我們初始化數(shù)據(jù)庫(kù),創(chuàng)建一個(gè)路由器實(shí)例,將變量 db 作為參數(shù)傳遞給我們的服務(wù)器初始化,然后將路由器實(shí)例作為參數(shù)傳遞給方法 registryRouter()。然后,我們使用偵聽(tīng)器來(lái)運(yùn)行服務(wù)器。
package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
"mybook/model"
)
func main() {
db, err := model.SetupDB()
if err != nil {
log.Println("Failed setting up database")
}
router := mux.NewRouter()
server := NewServer(db)
server.RegisterRouter(router)
log.Fatal(http.ListenAndServe(":8000", router))
}
當(dāng)寫(xiě)完所有的程序后,運(yùn)行 go run . 命令,啟動(dòng)成功如下:
此時(shí),運(yùn)行 cockcoach sql 進(jìn)入數(shù)據(jù)庫(kù)命令行,然后運(yùn)行 show tables; 命令,可以看到在默認(rèn)數(shù)據(jù)庫(kù) defaultdb 中已經(jīng)生成一個(gè) books 數(shù)據(jù)庫(kù)表(與我們的模型 books 同名),如下所示:
我們執(zhí)行 select * from books 查看我們生成的表信息:
可以看到已經(jīng)生成了我們定義的字段 id、name、author、description、price、category 以外,GORM 還默認(rèn)幫忙增加了 created_at (創(chuàng)建時(shí)間)、updated_at(更新時(shí)間)和 deleted_at(刪除時(shí)間)三個(gè)字段。
五、API 測(cè)試
為了方便我們測(cè)試這個(gè) book 應(yīng)用的 API 功能正常,我們將利用 APIfox 工具來(lái)進(jìn)行測(cè)試。先在數(shù)據(jù)庫(kù)中新增一條記錄,如下:
INSERT INTO books (id, name, author, description, price, category)
VALUES(1, 'Go程序設(shè)計(jì)語(yǔ)言(英文版)', '艾倫A.A.多諾萬(wàn) (Alan A.A.Donovan) / 布萊恩W.柯尼漢 (Brian W.Kemighan)', '被譽(yù)為 Go 語(yǔ)言圣經(jīng)的書(shū),非常值得一讀', 79.00, 'Golang');
獲取書(shū)籍清單:/books測(cè)試。
插入成功,我們?cè)L問(wèn)后臺(tái) http://127.0.0.1:8000/books 路徑,可以看到如下成功,說(shuō)明獲取書(shū)籍清單的 API 是成功的,恭喜。
接下來(lái)為了測(cè)試,??下載?? Linux 版本的 Apifox 幫助我們快速測(cè)試其他接口。
獲取一本書(shū)籍:/book/1 測(cè)試。
其他接口測(cè)試類似。
總結(jié)
本文利用 Go 語(yǔ)言中非常實(shí)用的 Gorilla Mux 和 GORM 庫(kù)、結(jié)合分布式 CockroachDB 數(shù)據(jù)庫(kù)編寫(xiě)了一個(gè)簡(jiǎn)易的圖書(shū)的 Restful API,最后通過(guò) Apifox 測(cè)試工具驗(yàn)證了服務(wù)器 API 的正確。學(xué)習(xí)了 CockroachDB 數(shù)據(jù)的安裝,了解到 Gorilla Mux 和 GORM 的使用方法。
顯然本文還是有很多不足,比如并沒(méi)有充分利用到 CockroachDB 數(shù)據(jù)庫(kù)的分布式特性,而且沒(méi)有為 API 編寫(xiě)測(cè)試代碼,測(cè)試并不一定完善,這些都可以溜給讀者一些優(yōu)化思路。
??想了解更多關(guān)于開(kāi)源的內(nèi)容,請(qǐng)?jiān)L問(wèn):??
?? 開(kāi)源基礎(chǔ)軟件社區(qū)??
??https://ost.??。
文章名稱:使用GorillaMux和CockroachDB編寫(xiě)可維護(hù)REST
本文URL:http://fisionsoft.com.cn/article/cosjcei.html


咨詢
建站咨詢
