使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API
source link: https://blog.51cto.com/yuzhou1su/5473859
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API
推荐 原创theme: devui-blue highlight: a11y-dark
本文利用到的 Go 语言相关技术:
- Gorilla/Mux:功能强大的 URL 路由器和调度组件
- CockroachDB:开源,云原生分布式 SQL 数据库系统
- GORM:神奇的 ORM 库
运行环境:
- Ubuntu 18.04.6 LTS
- 开发工具:Visual Studio Code
- 测试工具: APIfox:Apifox = Postman + Swagger + Mock + JMeter
本文将使用功能强大的 Gorilla Mux、GORM 和 CockroachDB 编写可维护 RESTful API。
1 CockroachDB 介绍
CockrocreDB 是一个云原生分布式 SQL 数据库,旨在构建,扩展和管理现代数据密集型应用程序。
官方介绍翻译如下:
CockroachDB 是一个分布式关系型数据库,建立在事务性和强一致性键值存储之上,具有水平扩展、高可用、支持强一致性 ACID 事务隔离级别的特点,并提供统一的 SQL API 用于结构化、操作和查询数据。
简单来说,CockroachDB 充分利用了上一代数据库系统的优势、SQL 的强大能力以及关系数据模型和现代云原生法则,成为了一个与其他基于 SQL 的事务型数据库广泛兼容的数据库系统。
CockroachDB 对 Go 也有很好的支持,比如 pgx、pg、GORM 和 upper/db。
1.1 下载安装
针对最新版的 CockroachDB Linux 下载,点 此处 可以看官方的教程写的很详细,这里本文将跟着照做一次。(选择其他系统,同理)
- 下载适用于 Linux 的 CockroachDB 包和其支持库,并将二进制文件复制到您的 PATH 中,以便您可以从任何 shell 执行 cockroach 命令:
如图所示:
- CockroachDB 使用 GEOS 库的定制版本。默认情况下,CockroachDB 在
/usr/local/lib/cockroach
或 CockroachDB 二进制文件当前目录的lib
子目录中查找外部链接库。
所以,您可以将二进制文件复制到您的路径中,以便您可以从任何目录执行 Cockroach 命令:
- 确保刚刚安装的 cockroach 已经成功安装:
- 启动使用启动临时本地内存集群,使用
cockroach demo
命令:
如果能看到上图,说明 CockroachDB 安装成功,恭喜~
1.2 使用本地集群
官方提供两种方式利用 GORM 使用 CockroachDB:一种是 CockroachDB Serverless(测试),另一种是使用本地集群。
第一种方式需要 注册一个 CockroachDB 云账号,然后按照官方提示创建链接。
本文将使用第二种方式:
- 运行
cockroach start-single-node
命令:
通过增加 --insecure
参数启动了一个不安全的单节点群集。
- 记录下 SQL Shell 中欢迎文本中以下连接信息:
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 <client cmd> --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
PS:这条信息需要加入到后续的数据库连接 datebase.go
文件中,请注意。
2 项目介绍
2.1 项目功能
本文创建一个应用是一个简单的 RESTful API 服务器:线上书店,它将公开书籍的访问和操作,主要包括如下功能:
- 创建一本书籍
- 获取书籍清单
- 获取一本书籍信息
- 更新已有书籍信息
- 删除一本书籍
2.2 API 接口规范
为了实现上述的功能,我们的应用需要提供给外界如下的 API 接口规范:
- 创建一本书籍:通过
/book
响应有效的 POST 请求 - 获取书籍清单:通过
/books
响应有效的 GET 请求 - 获取一本书籍:通过
/book/{id}
响应对应的 GET 请求 - 更新一本书籍:通过
/book/{id}
响应对应的 PUT 请求 - 删除一本书籍:通过
/book/{id}
响应对应的 DELETE 请求
通过 {id}
能够有效确定某本书籍。
豆瓣图书链接中
https://book.douban.com/subject/26859123/
的 26859123 就能对应到 《Go程序设计语言(英文版)》这本书。
2.3 项目结构
本文将创建一个非常简单的应用程序结构,这是本文使用的 mybook 应用的项目结构:
├── go.mod
├── go.sum
├── handlers.go
├── main.go
└── model
├── database.go
└── model.go
2.4 安装项目依赖
安装 go get -u github.com/gorilla/mux
,如下;
安装 go get -u gorm.io/gorm
安装 go get -u gorm.io/driver/postgres
3 应用功能设计
首先创建一个 mybook 的目录(应用文件夹),然后使用 go mod init mybook
,
接着在其中创建我们的 model 文件夹,接下来,在 model 文件夹下新建一个 model.go
文件。
3.1 model.go
我们需要设计出 book 模型,这里即将使用 GORM 进行创建,编写我们的 model.go
函数:
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"`
}
如上所示,书籍的结构体中进行简单设计包含 id(ID)、书名(Name)、作者(Author)、书籍描述(Description)、价格(Price)和类别(Category)。
3.2 database.go
该程序将创建数据库连接,需要使用到 postgresql://root@localhost:26257/defaultdb?sslmode=disable
传入到 gorm.Open()
的连接参数中,代码如下:
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.3 handler.go
回到 mybook 文件夹下,创建 handler.go
:
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!")
}
3.4 main.go
在 main.go
中,我们初始化数据库,创建一个路由器实例,将变量 db 作为参数传递给我们的服务器初始化,然后将路由器实例作为参数传递给方法 registryRouter()
。然后,我们使用侦听器来运行服务器。
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))
}
当写完所有的程序后,运行 go run .
命令,启动成功如下:
此时,运行 cockcoach sql
进入数据库命令行,然后运行 show tables;
命令,可以看到在默认数据库 defaultdb
中已经生成一个 books 数据库表,如下所示:
我们执行 select * from books
查看我们生成的表信息:
可以看到已经生成了我们定义的字段 id、name、author、description、price、category 以外,GORM 还默认帮忙增加了 created_at
(创建时间)、updated_at
(更新时间)和 deleted_at
(删除时间)三个字段。
4 API 测试
为了方便我们测试这个 book 应用的 API 功能正常,我们将利用 APIfox 工具来进行测试。先在数据库中新增一条记录,如下:
VALUES(1, 'Go程序设计语言(英文版)', '艾伦A.A.多诺万 (Alan A.A.Donovan) / 布莱恩W.柯尼汉 (Brian W.Kemighan)', '被誉为 Go 语言圣经的书,非常值得一读', 79.00, 'Golang');
获取书籍清单:/books
测试
插入成功,我们访问后台 http://127.0.0.1:8000/books
路径,可以看到如下成功,说明获取书籍清单的 API 是成功的,恭喜。
接下来为了测试, 下载 Linux 版本的 Apifox 帮助我们快速测试其他接口。
获取一本书籍:/book/1
测试
本文利用 Go 语言中非常实用的 Gorilla Mux 和 GORM 库、结合分布式 CockroachDB 数据库编写了一个简易的图书的 Restful API,最后通过 Apifox 测试工具验证了服务器 API 的正确。
显然本文还是有很多不足,比如并没有充分利用到 CockroachDB 数据库的分布式特性,而且没有为 API 编写测试代码,测试并不一定完善,这些都可以给到读者一些优化思路。
希望能对你有帮助,如果觉得文章不错,帮忙点个赞,谢谢,下一篇文章再见!
参考链接:
- 1赞
- 1收藏
- 评论
- 分享
- 举报
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK