• 不太一样的Go Web框架—编程范式


    项目地址: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中所有功能与主流程间的依赖都是解耦的,所有实现都面向接口,完全遵守依赖倒置原则。
    要实现这样的方式也不难:

    • 定义一套接口,使得实现相应接口的结构体都可以运行在linweb中。
    • 完成默认实现,默认应用默认实现在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中。

  • 相关阅读:
    算法题打卡day58-单调栈 | 739. 每日温度、496.下一个更大元素I
    pytho实例--pandas读取表格内容
    2022-11-17 更高效的Cascades优化器 - Columbia Query Optimizer
    kali工具熟悉——网络扫描
    Django_login界面之数据库连接
    git rebase 合并 commit 保持分支干净整洁
    dd命令测试硬盘IO
    FusionConpute虚拟机的发放与管理
    卡方分布和 Zipf 分布模拟及 Seaborn 可视化教程
    HTTP 错误 500.19 - Internal Server Error 无法访问请求的页面,因为该页的相关配置数据无效——错误代码:0x8007000d
  • 原文地址:https://www.cnblogs.com/codexiaoyi/p/16153914.html