Github传送门
学一门新语言,如果太专注于语法,那肯定学的不够快。如果有一定的需求目标,带着这个目标去学习一门新语言,这样才能学得快。
工作中写后端的时间比例不在少数,而且传闻Go也因其适用于服务端而著名。在Go社区中最为火热的服务端框架跟数据库抽象ORM层当属gin跟gorm了,因此本期单排的目标便是用gin+gorm打一个简易的框架,实现基本CRUD的目标。走你~
在第一话中已经大致了解了Go项目的生成,故本次gin+gorm的搭建也以此为基础。
服务端领域的各种术语在不同的场景跟技术栈下有不同的含义。但总体来讲,一个简单的服务端App都会有以下的层次:
基于以上结构,大致构建了以下的目录结构:
本次demo没有弄校验层(router里设置各种middleware),直接从handler开始干拉~
gorm实现了不同数据库对Go对象的映射。gorm本身提供了默认的表Model(id、增删改时间),但实际还是建议自行封装一层,以增加可控性。
这里以mysql交互为例,实现了一个封装例子。
// database/main.go
package database
type DBModel struct {
*gorm.Model
// You can add your own model components here
}
// DB database instance
var DBInstance *gorm.DB
// InitDB initialize DB
func InitDB() {
log.Println("Initializing database...")
// 从docker hub pull下来就好了= =
db, err := gorm.Open("mysql", "root:123456@tcp(localhost:32773)/gofromzero?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
panic(err)
}
DBInstance = db
}
假使我们创建一个User表,我们可以在另一个go文件内引用DBModel跟DBInstance,从而定义数据交互逻辑
// database/user.go
package database
// User表的结构
type User struct {
*DBModel
Name string `json:"name"`
Age uint `json:"age"`
}
type userDAO struct {}
// UserDAO user dao
var UserDAO userDAO
// Create create a user record
func (*userDAO) Create(user User) error {
DBInstance.AutoMigrate(&User{})
return DBInstance.Create(&user).Error
}
// First get the first record of user
func (*userDAO) First() (User, error) {
var user User
err := DBInstance.First(&user).Error
return user, err
}
// Update update user record
func (*userDAO) Update(user User) error {
return DBInstance.Model(&User{}).Updates(&user).Error
}
// Delete set all to delete state
func (*userDAO) Delete() error {
return DBInstance.Delete(&User{}).Error
}
这样在外部要跟User表交互的话,直接用UserDAO
就可以完成需求了
有了对gorm的进一步封装,我们就不需过多担心数据获取的问题。现在我们可以关注gin的写法。
首先建议封装一些常用的返回方法,什么success、error之类,有助于减少码量。
// handler/base.go
package handler
func Success(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, gin.H{
"data": data,
})
}
func Error(c *gin.Context, err error, code int) {
c.JSON(code, gin.H{
"err": err.Error(),
})
}
而后在user模块的handler下,实现控制器逻辑:
// handler/user.go
package handler
import (
"github.com/gin-gonic/gin"
service "github.com/gofromzero/ii/service/user"
"net/http"
)
type user struct{}
// User instance of user controller
var User user
// Create create a user
func (*user) Create(c *gin.Context) {
var userForm service.Form
bindErr := c.ShouldBindJSON(&userForm)
if bindErr != nil {
Error(c, bindErr, http.StatusForbidden)
return
}
createErr := service.Create(userForm)
if createErr != nil{
Error(c, createErr, http.StatusBadRequest)
return
}
Success(c, "Create user successfully!")
}
// 下略,CRUD
我们可以看到我们通过一个文件夹定义了user业务层的模块。业务层代码如下:
// service/user/crud.go
package user
type Form struct {
Name string `json:"name"`
Age uint `json:"age"`
}
// Create create user on form
func Create(form Form) error {
return database.UserDAO.Create(database.User{
Name: form.Name,
Age: form.Age,
})
}
// First get first user from database
func First() (database.User, error) {
return database.UserDAO.First()
}
// Update update user records
func Update(form Form) error {
return database.UserDAO.Update(database.User{
Name: form.Name,
Age: form.Age,
})
}
// Delete delete user records
func Delete() error {
return database.UserDAO.Delete()
}
最后,在路由中调用handler.User
,我们就能很轻松地注册user模块的控制器:
// router.go
// Router gin router
func Router() *gin.Engine {
log.Println("Registering routers...")
r := gin.Default()
api := r.Group("/api")
v1 := api.Group("/v1")
{
user := v1.Group("/user")
{
user.POST("", handler.User.Create)
user.GET("", handler.User.Get)
user.PUT("", handler.User.Update)
user.DELETE("", handler.User.Delete)
}
}
return r
}
启动App实例:
// app.go
// StartGin start gin server
func StartGin() {
database.InitDB()
defer database.CloseDB()
router := Router()
s := &http.Server{
Addr: ":8080",
Handler: router,
}
err := s.ListenAndServe()
if err != nil {
panic(err)
}
}
试试看吧~
Go在整体印象上真的很严格,甚至比Java还严格。这part的代码写起来真心不如JS轻松,但通过这些换来代码健壮性与服务性能增益,是很值得的。
这次项目架构的设计也仅是抛砖引玉,对gorm以及gin还没有更加深入的探究。以后可得加油~