项目地址:https://github.com/Codexiaoyi/linweb
上文说过,linweb不追求性能,相比而言注重编程范式。本人也是dotneter,个人觉得.net那种注解定义路由的方式更为舒服,并且接口文件统一规定在Controller文件夹下,以 XxxController 命名。
当然,在Go中也是可以实现这样的方式,但是将用到大量反射,所以势必会降低性能,所以说"不太一样的Web框架"。
所谓编程范式,也就是你的框架定义规范,使用用户按照你的规范写逻辑业务。
路由解析是一个web框架不可避免的模块,我们看gin是如何定义路由的。
func main() { router := gin.Default() // Simple group: v1 v1 := router.Group("/v1") { v1.POST("/login", loginEndpoint) v1.POST("/submit", submitEndpoint) v1.POST("/read", readEndpoint) } // Simple group: v2 v2 := router.Group("/v2") { v2.POST("/login", loginEndpoint) v2.POST("/submit", submitEndpoint) v2.POST("/read", readEndpoint) } router.Run(":8080") }
在应用gin的时候,我们通常会将路由定义与接口方法对应写在一起,统一管理,这样管理方便,也是Go的web框架多数采用的方式。
相比之下,linweb更希望路由定义和方法放在一起,然后以定义Controller的方式,路由地址更加直观。
package controllers import ( "github.com/Codexiaoyi/linweb/interfaces" ) type BlogController struct { } //[GET("/blog/:id")] func (blog *BlogController) GetBlog(c interfaces.IContext) { }
在根目录下创建一个 controllers 文件夹,所有的api都定义在controllers包中。根据不同的 controller 名称区分文件。
linweb中所有功能与主流程间的依赖都是解耦的,所有实现都面向接口,完全遵守依赖倒置原则。
要实现这样的方式也不难:
在Go语言框架中大量使用Option模式实现上述的需求(完整代码详见github)。
1.首先我们定义一个自定义插件类型,并写一个默认的插件函数。
//自定义插件类型,返回可以传入Linweb的函数 type CustomizePlugins func(lin *Linweb) func defaultPlugins() CustomizePlugins { return func(lin *Linweb) { lin.markRouter = router.New() lin.markContext = &context.Context{} lin.markMiddleware = &middleware.Middleware{} lin.markInject = injector.Instance() lin.markCache = cache.Instance() lin.markModel = &model.Model{} } }
2.定义各个插件模块对应的快捷入口函数。
// Customize router plugin.这里参数传入自定义的Router实现 func RouterPlugin(router interfaces.IRouter) CustomizePlugins { return func(lin *Linweb) { lin.markRouter = router } } // Customize context plugin. func ContextPlugin(context interfaces.IContext) CustomizePlugins { return func(lin *Linweb) { lin.markContext = context } } ......
3.在linweb包的New初始化函数中,传入用户自定义的CustomizePlugins。
// Create a new Linweb. // Add customize plugins with method of plugins.go, otherwise use default plugins. func NewLinweb(plugins ...CustomizePlugins) *Linweb { lin := &Linweb{} //应用默认插件 defaultPlugins()(lin) //根据传入的用户自定义插件覆盖默认插件 for _, plugin := range plugins { plugin(lin) } pluginsModel = lin.markModel Cache = lin.markCache return lin }
在linweb项目中的linweb_test.go中,用到了mock框架(后续测试部分会介绍)模拟接口实现:
// Arrange:mock data ctrl := gomock.NewController(t) defer ctrl.Finish() // mock a new context instance mock_context := mocks.NewMockIContext(ctrl) //Act linweb := NewLinweb(ContextPlugin(mock_context))
如代码所见,我们调用linweb的ContextPlugin函数传入自定义插件,再将返回值传入NewLinweb初始化函数中。
本文,介绍了linweb的基本的编程范式,也实现了自定义插件功能。
接下来,我们需要根据前文说的功能逐步添加到linweb中。