11

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

 2 years ago
source link: https://www.cnblogs.com/codexiaoyi/p/16153914.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

不太一样的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中。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK