写点什么

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

作者:宇宙之一粟
  • 2022 年 7 月 14 日
  • 本文字数:4935 字

    阅读完需:约 16 分钟

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

本文利用到的技术:


  • Gorilla/Mux:功能强大的 URL 路由器和调度组件

  • CockroachDB:开源,云原生分布式 SQL 数据库系统

  • GORM:神奇的 ORM 库

  • APIfox:Apifox = Postman + Swagger + Mock + JMeter


本文运行环境:


  • Ubuntu 18.04.6 LTS


本文将使用功能强大的 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 下载,点此处 可以看官方的教程写的很详细,这里本文将跟着照做一次。(选择其他系统,同理)


  1. 下载适用于 Linux 的 CockroachDB 包和其支持库,并将二进制文件复制到您的 PATH 中,以便您可以从任何 shell 执行 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/
复制代码


如图所示:



  1. CockroachDB 使用 GEOS 库的定制版本。默认情况下,CockroachDB 在 /usr/local/lib/cockroach 或 CockroachDB 二进制文件当前目录的 lib 子目录中查找外部链接库。


所以,您可以将二进制文件复制到您的路径中,以便您可以从任何目录执行 Cockroach 命令:


sudo cp -R cockroach-v22.1.3.linux-amd64/* /usr/local/bin
复制代码


  1. 确保刚刚安装的 cockroach 已经成功安装:


which cockroach
复制代码



  1. 启动使用启动临时本地内存集群,使用 cockroach demo 命令:



如果能看到上图,说明 CockroachDB 安装成功,恭喜~

1.2 使用本地集群

官方提供两种方式利用 GORM 使用 CockroachDB:一种是 CockroachDB Serverless(测试),另一种是使用本地集群。



第一种方式需要注册一个 CockroachDB 云账号,然后按照官方提示创建链接。


本文将使用第二种方式:


  1. 运行 cockroach start-single-node 命令:


cockroach start-single-node --advertise-addr 'localhost' --insecure
复制代码


通过增加 --insecure 参数启动了一个不安全的单节点群集。


  1. 记录下 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:8080sql:                 postgresql://root@localhost:26257/defaultdb?sslmode=disablesql (JDBC):          jdbc:postgresql://localhost:26257/defaultdb?sslmode=disable&user=rootRPC client flags:    cockroach <client cmd> --host=localhost:26257 --insecurelogs:                /home/yuzhou/cockroach-data/logstemp dir:            /home/yuzhou/cockroach-data/cockroach-temp2801178939external I/O path:   /home/yuzhou/cockroach-data/externstore[0]:            path=/home/yuzhou/cockroach-datastorage engine:      pebbleclusterID:           43919fea-32cd-48f9-b585-713731d43ee9status:              restarted pre-existing nodenodeID:              1
复制代码


其中,postgresql://root@localhost:26257/defaultdb?sslmode=disable ,通过 SQL 连接字符串连接我们的 CockroachDB 集群中。


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 函数:


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"`}
复制代码


如上所示,书籍的结构体中进行简单设计包含 id(ID)、书名(Name)、作者(Author)、书籍描述(Description)、价格(Price)和类别(Category)。

3.2 database.go

该程序将创建数据库连接,需要使用到 postgresql://root@localhost:26257/defaultdb?sslmode=disable 传入到 gorm.Open() 的连接参数中,代码如下:


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.Panicln(now) // 根据 Book 模型自动创建 "Book" 表 if err = db.AutoMigrate(&Book{}); err != nil { log.Println(err) } return db, err}
复制代码

3.3 handler.go

回到 mybook 文件夹下,创建 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.NewDecoder(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); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode("Book Deleted Successfully!")}
复制代码

main.go

main.go 中,我们初始化数据库,创建一个路由器实例,将变量 db 作为参数传递给我们的服务器初始化,然后将路由器实例作为参数传递给方法 registryRouter()。然后,我们使用侦听器来运行服务器。


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))
}
复制代码


启动成功如下:


总结


参考链接:



启动成功


本文正在参加技术专题18期-聊聊Go语言框架

发布于: 刚刚阅读数: 4
用户头像

宇宙古今无有穷期,一生不过须臾,当思奋争 2020.05.07 加入

🏆InfoQ写作平台-第二季签约作者 🏆 混迹于江湖,江湖却没有我的影子 热爱技术,专注于后端全栈,轻易不换岗 拒绝内卷,工作于软件工程师,弹性不加班 热衷分享,执着于阅读写作,佛系不水文

评论

发布
暂无评论
使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API_Go 语言_宇宙之一粟_InfoQ写作社区