使用Golang处理应用配置的App Handler实现

使用Golang处理应用配置的App Handler实现 我尝试创建 AppHandler {Config: AppConfig, Handler: func()} 但效果并不理想。它虽然能工作,但看起来非常不美观。

之前我是这样使用的:

t := templates.LoadTemplates() // 此函数使用 parseglob 加载所有 html 文件… 我使用了一个中间件,并将这个 “t” 附加到上下文中。

对于数据库,我也需要做同样的事情。 对于本地化器以及所有其他组件,我都需要做同样的事情。

所以,现在我考虑将所有内容都放在配置结构体中。

type Config struct {
T *template.Template
DB *sql.DB
}

现在,对于每个路由,我又必须传递这个配置。 与其这样,我更希望有一个主路由来持有这个配置文件。 这个主路由将处理所有路由。在 Go 中,我应该如何实现这种方式?

接口可以包含配置细节吗? 这样我就可以创建一个契约并访问配置?

请分享一些你的代码或实现这个想法的思路。


更多关于使用Golang处理应用配置的App Handler实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

正如许多人所说,如果我使用Go,就必须靠自己。它没有支持。Django有非常好的支持。我真的很困惑……静态类型和高性能让我坚持使用这门语言。

更多关于使用Golang处理应用配置的App Handler实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我只接触Go语言几个月……这是成果……示例 或许能帮上忙……

你在每个文件中都写了数据库连接。我认为这不是一个好主意。

请参考我的问题。将数据库实现在单独的包中,并在主页面导入。 最后,你需要将其传递给每个处理器。这就是挑战所在。

你好。我建议尝试在 http.Server 周围添加一个包装器,并使用方法作为处理器。类似这样:

type Server struct {
    srv http.Server
    cfg Config // 你的配置
}

func (s *Server) Handler(w http.ResponseWriter, r *http.Request) {
    // 通过 s.cfg 直接访问你的配置并进行操作...
}

正如许多人所说,如果我使用Go,我必须自己使用它。没有支持。

无论你选择哪种语言,你都必须自己(或与你的团队一起)构建你的应用程序。我发现社区(这里、Reddit上和Discord里!)非常支持。话虽如此,我不太确定你想实现什么以及你的问题是什么。听起来你试图将配置传递给路由,并且不确定在哪里/如何存储你的数据库连接池?

DB 对象

我认为这是全局变量可以接受的领域之一。例如,拥有一个名为 DB 的全局SQL连接池对象是我见过很多次的模式。我经常发现将数据访问层提取到它自己的包中很有用。这允许我根据需要将其重新导出到多个可执行文件中。例如:我有一个项目,其中有一个调度器服务在运行,它使用与我的主Web可执行文件相同的查询/数据访问层来执行诸如发送计划电子邮件和每晚构建资源密集型报告等任务。

这是一个虚构的 queries 包来演示这一点:

// Package queries provides data access layer for app.
package queries

import (
	"database/sql"

	_ "github.com/go-sql-driver/mysql"
)

// Global connection pool object
var db *sql.DB

// InitDatabase sets the global connection pool object to allow 
// execution of SQL queries. Call `ShutdownDB` to close.
func InitDB(connString string) error {
	conn, err := sql.Open("mysql", connString)
	if err != nil {
		return err
	}
	// Set our global db oject
	db = conn
	return nil
}

// ShutdownDB calls `Close()` on global connection pool object.
func ShutdownDB() error {
	return db.Close()
}

然后,在你初始化路由等的主包中,确保也初始化查询并关闭:

package main

import (
	"github.com/my-package/queries"
)

func main() {
	// Initialize connection pool in queries. In the real world, handle errors
	err := queries.InitDB("user:password@/dbname")
	// Close our DB connection pool on exit
	defer queries.ShutdownDB()
}

正如我上面提到的,这样做的好处是:将来任何包都可以重用你的数据访问层/查询。我总是能找到它的用途。例如,我创建Go可执行文件来执行脚本任务。我创建可执行文件来与外部API同步数据,当数据不同步时。等等。你的用例可能和我的不一样,但是,我发现将数据层放在一个可重用的包中有很大的价值。在一个地方搜索SQL查询也很方便。

模板 / 请求上下文

关于你的模板问题,我想知道你是否只需要一个围绕处理函数的包装器,该包装器包含模板以及该路由特有的任何其他内容?让我们创建一个包含我们想要的额外数据的函数签名:

// Configuration for our route. Expect one for each route/template.
type RouteConfig struct {
	Template *template.Template
}

// TemplateHandle is like http.HandlerFunc, but has a third parameter for config.
type TemplateHandle func(http.ResponseWriter, *http.Request, RouteConfig)

… 然后让我们创建一个包装器,将它们转换为 http.HandlerFunc

// newHandler wraps our TemplateHandle func
func newTemplateHandler(h TemplateHandle, c RouteConfig) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		h(w, r, c)
	}
}

… 然后让我们使用我们新的 TemplateHandle 包装器:

// Handler for our home page
func Home(w http.ResponseWriter, r *http.Request, c RouteConfig) {
	c.Template.Execute(w, nil)
}

// Handler for our user detail page
func User(w http.ResponseWriter, r *http.Request, c RouteConfig) {
	c.Template.Execute(w, nil)
}

func main() {
	t := template.Must(template.New("home").Parse("<h1>Hello Home!</h1>"))
	// Config for home route
	homeConfig := RouteConfig{t}
	// Wrapped handler for home route
	http.HandleFunc("/", newTemplateHandler(Home, homeConfig))

	// Config for our /user/ route.
	userTemplate := template.Must(template.New("user").Parse("<h1>User Detail</h1>"))
	// Config for home route
	userConfig := RouteConfig{userTemplate}
	http.HandleFunc("/user", newTemplateHandler(User, userConfig))
	log.Fatal(http.ListenAndServe(":8088", nil))
}

还有其他方法,但这应该能让你入门。作为另一种选择,看看这个:

来源: stackoverflow.com

标题: How do I pass arguments to my handler

标签: go, gorilla

你也可以为你的路由创建一个 http.Handler

来源: pkg.go.dev

标题: http package - net/http - Go Packages

描述: Package http provides HTTP client and server implementations.

在你的情况下,你的处理程序结构将内置模板并满足 ServeHTTP。总之,有很多选择。我认为你困惑的是:Go对你编写/格式化代码的方式有意见,但对你如何构建项目有意见。如果你想使用一个更有主见的框架,有选项。也许看看这个Reddit帖子找找想法?

在Go中处理应用配置和依赖注入,一个常见的做法是使用结构体封装依赖,并通过方法接收器传递。以下是几种实现方式:

1. 基础应用结构体模式

package main

import (
    "database/sql"
    "html/template"
    "net/http"
)

type AppConfig struct {
    Templates *template.Template
    DB        *sql.DB
    Locale    *Localizer
    // 其他依赖
}

type AppHandler struct {
    Config *AppConfig
    Handle func(*AppConfig, http.ResponseWriter, *http.Request)
}

func (ah AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    ah.Handle(ah.Config, w, r)
}

// 使用示例
func main() {
    config := &AppConfig{
        Templates: templates.LoadTemplates(),
        DB:        initDB(),
        Locale:    initLocalizer(),
    }
    
    http.Handle("/home", AppHandler{
        Config: config,
        Handle: homeHandler,
    })
    
    http.Handle("/profile", AppHandler{
        Config: config,
        Handle: profileHandler,
    })
    
    http.ListenAndServe(":8080", nil)
}

func homeHandler(config *AppConfig, w http.ResponseWriter, r *http.Request) {
    config.Templates.ExecuteTemplate(w, "home.html", nil)
    // 可以使用 config.DB, config.Locale 等
}

2. 闭包工厂模式(更优雅)

package main

import (
    "database/sql"
    "html/template"
    "net/http"
)

type App struct {
    Templates *template.Template
    DB        *sql.DB
    Locale    *Localizer
}

// 创建处理函数,闭包捕获应用实例
func (app *App) HomeHandler() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // 直接访问 app.Templates, app.DB 等
        data := map[string]interface{}{
            "Title": "Home Page",
        }
        app.Templates.ExecuteTemplate(w, "home.html", data)
    }
}

func (app *App) ProfileHandler() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // 使用数据库
        var username string
        err := app.DB.QueryRow("SELECT username FROM users WHERE id = $1", 1).Scan(&username)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        
        data := map[string]interface{}{
            "Username": username,
            "Greeting": app.Locale.Translate("welcome"),
        }
        app.Templates.ExecuteTemplate(w, "profile.html", data)
    }
}

// 使用示例
func main() {
    app := &App{
        Templates: templates.LoadTemplates(),
        DB:        initDB(),
        Locale:    initLocalizer(),
    }
    
    http.HandleFunc("/home", app.HomeHandler())
    http.HandleFunc("/profile", app.ProfileHandler())
    http.HandleFunc("/api/data", app.DataAPIHandler())
    
    http.ListenAndServe(":8080", nil)
}

3. 接口契约模式

package main

import (
    "database/sql"
    "html/template"
    "net/http"
)

// 定义配置接口
type AppContext interface {
    Templates() *template.Template
    DB() *sql.DB
    Locale() *Localizer
    Logger() *Logger
}

// 具体实现
type App struct {
    templates *template.Template
    db        *sql.DB
    locale    *Localizer
    logger    *Logger
}

func (a *App) Templates() *template.Template { return a.templates }
func (a *App) DB() *sql.DB                   { return a.db }
func (a *App) Locale() *Localizer            { return a.locale }
func (a *App) Logger() *Logger               { return a.logger }

// 处理函数类型
type HandlerFunc func(AppContext, http.ResponseWriter, *http.Request)

// 适配器
func (app *App) MakeHandler(h HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        h(app, w, r)
    }
}

// 使用接口的处理函数
func homeHandler(ctx AppContext, w http.ResponseWriter, r *http.Request) {
    ctx.Logger().Log("Accessing home page")
    data := map[string]interface{}{
        "Message": ctx.Locale().Translate("hello"),
    }
    ctx.Templates().ExecuteTemplate(w, "home.html", data)
}

func userHandler(ctx AppContext, w http.ResponseWriter, r *http.Request) {
    var count int
    err := ctx.DB().QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
    if err != nil {
        ctx.Logger().Error("Database error:", err)
        http.Error(w, "Internal error", http.StatusInternalServerError)
        return
    }
    
    w.Write([]byte("Total users: " + string(count)))
}

// 使用示例
func main() {
    app := &App{
        templates: loadTemplates(),
        db:        initDB(),
        locale:    initLocalizer(),
        logger:    NewLogger(),
    }
    
    http.HandleFunc("/home", app.MakeHandler(homeHandler))
    http.HandleFunc("/users", app.MakeHandler(userHandler))
    
    http.ListenAndServe(":8080", nil)
}

4. 使用中间件传递配置

package main

import (
    "context"
    "database/sql"
    "html/template"
    "net/http"
)

type App struct {
    Templates *template.Template
    DB        *sql.DB
    Locale    *Localizer
}

// 上下文键类型
type contextKey string

const appKey contextKey = "app"

// 中间件注入App实例
func (app *App) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := context.WithValue(r.Context(), appKey, app)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// 从上下文中获取App
func GetApp(r *http.Request) *App {
    if app, ok := r.Context().Value(appKey).(*App); ok {
        return app
    }
    return nil
}

// 处理函数
func homeHandler(w http.ResponseWriter, r *http.Request) {
    app := GetApp(r)
    if app == nil {
        http.Error(w, "App not found", http.StatusInternalServerError)
        return
    }
    
    app.Templates.ExecuteTemplate(w, "home.html", nil)
}

// 使用示例
func main() {
    app := &App{
        Templates: loadTemplates(),
        DB:        initDB(),
        Locale:    initLocalizer(),
    }
    
    mux := http.NewServeMux()
    mux.HandleFunc("/home", homeHandler)
    mux.HandleFunc("/profile", profileHandler)
    
    // 包装整个路由器
    wrappedMux := app.Middleware(mux)
    
    http.ListenAndServe(":8080", wrappedMux)
}

推荐方案

第二种闭包工厂模式最为简洁实用,它:

  • 保持处理函数签名标准(http.HandlerFunc
  • 通过闭包自然访问应用依赖
  • 代码结构清晰,易于测试
  • 符合Go的惯用法
// 这是最推荐的实现方式
func (app *App) RegisterRoutes() {
    http.HandleFunc("/", app.HomeHandler())
    http.HandleFunc("/users", app.UsersHandler())
    http.HandleFunc("/api/data", app.DataHandler())
}

// 每个处理函数都这样定义
func (app *App) UsersHandler() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // 直接使用 app.DB, app.Templates 等
        users := app.fetchUsers()
        app.Templates.ExecuteTemplate(w, "users.html", users)
    }
}

这种方法避免了全局变量,依赖关系明确,且易于进行单元测试(可以传入模拟的App实例)。

回到顶部