不太一樣的Go Web框架—程式設計正規化

aaayi發表於2022-04-16

專案地址: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中。

相關文章