
前言
在学习 golang web 开发的过程中,看着 gin 框架中关于路由的示例,就思考着在实际项目开发过程中肯定不会将路由、路由群组的加载都集中在 main 函数中,如果路由很少还好,否则业务一旦多起来,所有业务的路由都混杂在一起,耦合度那就太高了,根本不利于后期维护。于是,研究了几个开源的 golang 项目,这里总结一下常见的路由管理模式(路由的组织方式)。
路由是什么
Web 应用开发中的路由就是 URL 与 资源的对应关系,资源可以是静态文件、API、服务、数据等等。路由分前端路由和服务端路由,本文讨论的是服务端路由。
中心化管理
顾名思义,就是将项目中的路由集中声明在约定的路由文件中,然后一起装载到 http 服务中,比如开源项目 Go语言中文网 中的路由组织方式:
在 http/controller/
目录下每一个 controller
中都有个方法 RegisterRoute
用来管理当前 controller
下的路由信息
// ...
type ArticleController struct{}
// 注册路由
func (self ArticleController) RegisterRoute(g *echo.Group) {
g.GET("/articles", self.ReadList)
g.GET("/articles/crawl", self.Crawl)
g.GET("/articles/:id", self.Detail)
g.Match([]string{"GET", "POST"}, "/articles/new", self.Create, middleware.NeedLogin(), middleware.Sensivite(), middleware.BalanceCheck(), middleware.PublishNotice(), middleware.CheckCaptcha())
g.Match([]string{"GET", "POST"}, "/articles/modify", self.Modify, middleware.NeedLogin(), middleware.Sensivite())
}
// ...
然后在 http/controller/routes.go
中集中将各个 controller
中的方法 RegisterRoute
注册到路由群组中
package controller
import echo "github.com/labstack/echo/v4"
func RegisterRoutes(g *echo.Group) {
new(IndexController).RegisterRoute(g)
new(AccountController).RegisterRoute(g)
new(TopicController).RegisterRoute(g)
new(ArticleController).RegisterRoute(g)
new(ProjectController).RegisterRoute(g)
new(ResourceController).RegisterRoute(g)
new(ReadingController).RegisterRoute(g)
new(WikiController).RegisterRoute(g)
new(UserController).RegisterRoute(g)
new(LikeController).RegisterRoute(g)
new(FavoriteController).RegisterRoute(g)
new(MessageController).RegisterRoute(g)
new(SidebarController).RegisterRoute(g)
new(CommentController).RegisterRoute(g)
new(SearchController).RegisterRoute(g)
// ...
}
最后在 main
函数中调用各个 routes.go
中函数 RegisterRoutes
将路由群组注册到 http 服务中
// ...
func main(){
// ...
e := echo.New()
serveStatic(e)
e.Use(thirdmw.EchoLogger())
e.Use(mw.Recover())
e.Use(pwm.Installed(filterPrefixs))
e.Use(pwm.HTTPError())
e.Use(pwm.AutoLogin())
// 评论后不会立马显示出来,暂时缓存去掉
// frontG := e.Group("", thirdmw.EchoCache())
frontG := e.Group("")
controller.RegisterRoutes(frontG)
adminG := e.Group("/admin", pwm.NeedLogin(), pwm.AdminAuth())
admin.RegisterRoutes(adminG)
// appG := e.Group("/app", thirdmw.EchoCache())
appG := e.Group("/app")
app.RegisterRoutes(appG)
// ...
}
// ...
golang 就是这么简单,没那么多奇技淫巧,简简单单几行代码就把路由搞定了,不像 java 那么多类,类之间那么复杂,即使 springboot 用起来算是上手容易了,但是后面开发过程中也要了解很多注解之类的,阅读源码理清里面的点点滴滴,只能呵呵了…
搜了一些其他的开源项目比如 golang123 和 DocHub 中的路由组织方式:
都是把路由声明在一个路由文件中,例如:
// ...
// Route 路由
func Route(router *gin.Engine) {
apiPrefix := config.ServerConfig.APIPrefix
api := router.Group(apiPrefix, middleware.RefreshTokenCookie)
{
api.GET("/siteinfo", common.SiteInfo)
api.POST("/signin", user.Signin)
api.POST("/signup", user.Signup)
api.POST("/signout", middleware.SigninRequired, user.Signout)
api.POST("/upload", middleware.SigninRequired, common.UploadHandler)
api.POST("crawlnotsavecontent", middleware.EditorRequired, crawler.CrawlNotSaveContent)
api.POST("/active/sendmail", user.ActiveSendMail)
api.POST("/active/user/:id/:secret", user.ActiveAccount)
api.POST("/reset/sendmail", user.ResetPasswordMail)
api.GET("/reset/verify/:id/:secret", user.VerifyResetPasswordLink)
api.POST("/reset/password/:id/:secret", user.ResetPassword)
// ...
}
adminAPI := router.Group(apiPrefix+"/admin", middleware.RefreshTokenCookie, middleware.AdminRequired)
{
adminAPI.POST("/keyvalueconfig", keyvalueconfig.SetKeyValue)
adminAPI.GET("/users", user.AllList)
adminAPI.GET("/books/categories", category.BookCategoryList)
adminAPI.POST("/books/categories/create", category.CreateBookCategory)
adminAPI.GET("/categories", category.List)
adminAPI.POST("/categories/create", category.Create)
adminAPI.PUT("/categories/update", category.Update)
// ...
}
}
// ...
集中式声明路由的方式中,个人比较喜欢 Go语言中文网 的方式,具体的路由是在各自的 controller 中定义的,如果需要在 controller 中新增路由则只需修改当前的 controller 即可,而后面的两个开源项目每新增一个路由都需要修改至少两个地方。但是这种将路由集中声明在一起的方式,如果需要添加新的 controller 都至少需要修改两个地方,如果项目比较复杂,涉及的开发人员较多,而路由需要频繁修改更新的话,那么约定的路由文件发生冲突的可能性会大大增加。
总结
优点:将路由集中声明在约定的路由文件中,能够很直观的观察到所有路由规划,思路上清晰,逻辑上简单,上手容易,易于在团队内推广
缺点:新增 controller 需要修改至少两处地方,项目复杂、开发人员多时有增大冲突的风险
去中心化管理
相对于将路由都集中声明在一起与其实现逻辑分开来,我更喜欢 java 中类似 servlet3.0 和 spring 中直接将路由以注解的方式和其实现的业务直接声明在一起,类似的还有 nodejs 中 nestjs 框架,通过 装饰器 模式来实现将路由和业务实现声明在一起。这种声明方式的好处就是,路由和业务实现在一起,无论是新增还是修改,在开发体验上都是一气呵成的,一般在 main 函数中也看不到注册具体路由的声明,而至于 路由 和 实现函数 怎么绑定在一起,就交由框架去管理吧,开发人员只负责按约定规则编写即可
可惜 golang 中没有 注解和装饰器 这种语法 ~
在 github 上查了一些开源项目,没有发现类似的实现或者相关轮子,于是自己简单的实现了一下(针对 gin 框架实现的,很容易改成其他 go web 框架使用)。目标就是将路由分散的声明放在当前的 controller 中。
之前库为 github.com/niqingyang/ginboot,2019/10/19 迁移到了 gokit 中
之前库为 github.com/gokit/ginboot,2020/07/15 变更项目名称
Ginboot
GinBoot 是一个能够简化 Gin 框架下路由管理的库
核心思想是通过 GinBoot 注册 Gin 的实例和路由群组,然后在每个 Controller 的 init 中各自注册自己的路由回调函数,再统一由 GinBoot 控制群组和路由回调函数的加载顺序,减少路由间的耦合
GinBoot 是非线程安全的(因为 GinBoot 的业务需要在 Gin 服务启动前加载并运行完毕,所以无需考虑并发,也不建议在此期间出现并发逻辑)
安装
$ go get -u github.com/gokit/gin
快速开始
- 在 main 函数中按下面模板编写,为 ginboot 注册 gin 实例,并调用 Strap 函数引导注册的回调函数初始化路由
import (
"github.com/gin-gonic/gin"
"github.com/gokit/gin/boot"
_ "github.com/gokit/gin/example/controller" // load init function
)
func main() {
r := gin.Default()
boot.Strap(r)
r.Run(":8080")
}
- 定义 controller,编写 init 函数,在其中通过 boot.Route() 注入声明路由的回调函数
package controller
mport (
"github.com/gin-gonic/gin"
"github.com/gokit/gin/boot"
)
type IndexController struct{}
func init() {
var controller IndexController
boot.Route(func(r *gin.Engine) {
r.GET("/", controller.index)
})
}
func (IndexController) index(ctx *gin.Context) {
ctx.String(200, "hello world")
}
- 如果需要定义路由群组,可以在 init 函数中,通过 boot.GroupByName() 注入声明路由群组的回调函数
package controller
import (
"github.com/gin-gonic/gin"
"github.com/gokit/gin/boot"
)
type AdminController struct{}
func init() {
var controller AdminController
boot.GroupByName(“/admin”, func(group *gin.RouterGroup) {
group.GET("/", controller.index)
})
}
func (AdminController) index(ctx *gin.Context) {
ctx.String(200, "hello admin")
}
- 最后一步,在 main 函数中,通过 “_” 的方式 import 相关包,就大功告成了 ~
// 加载 controller 包下的 init 函数,声明路由和路由群组相关的回调函数
import _ "xxx/xxx/controller"
接口说明
ginboot 包下函数说明
// 注册路由群组
func AddGroup(name string, group *gin.RouterGroup)
// 获取指定路由群组
func GetGroup(name string) *gin.RouterGroup
// 向指定的 GIN 实例中注入中间件回调
func Middleware(callback func(app *gin.Engine))
// 向指定的 GIN 实例中注入路由回调
func Route(callback func(app *gin.Engine))
// 向指定的 GIN 实例中注入路由群组回调
func Group(callback func(app *gin.Engine))
// 向指定的路由群组中中注入路由回调
func GroupByName(groupName string, callback func(group *gin.RouterGroup))
// 在注册 Fiber 实例后,启用引导程序引导回调
func Strap(instance *gin.Engine)
以上就是我在 golang 中希望的路由管理模式,期待有更好的实现方式来替代这个简单的库 ~
本文作者: 行风
本文链接: https://acme.top/go-web-router
版权声明: 本站文章欢迎链接分享,禁止全文转载!
发表评论
沙发空缺中,还不快抢~