You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go中间件中优雅注入Repository或Service的最佳实践探讨

在Go认证Middleware中注入Repository/Service的最佳实践

核心思路:依赖注入的两种常用方式

1. 闭包注入(最简洁通用)

这是Go生态里最常见的实现方式,利用闭包把依赖的Repository/Service封装进Middleware函数,既避免全局变量,又保持Middleware的灵活性。

示例代码:

// 定义用户Repository抽象接口,遵循依赖倒置原则
type UserRepository interface {
    FindByToken(token string) (*User, error)
}

// 构造认证Middleware的工厂函数,接收Repository作为参数
func AuthMiddleware(repo UserRepository) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // 从请求头获取认证token
            token := r.Header.Get("Authorization")
            if token == "" {
                http.Error(w, "Unauthorized", http.StatusUnauthorized)
                return
            }

            // 调用注入的Repository方法验证token
            user, err := repo.FindByToken(token)
            if err != nil || user == nil {
                http.Error(w, "Unauthorized", http.StatusUnauthorized)
                return
            }

            // 将认证通过的用户信息存入请求上下文,供后续Handler使用
            ctx := context.WithValue(r.Context(), "authUser", user)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// 使用示例:先初始化具体Repository,再生成Middleware
func main() {
    userRepo := NewPostgresUserRepository() // 实际的数据库实现
    authMiddleware := AuthMiddleware(userRepo)

    mux := http.NewServeMux()
    mux.Handle("/protected", authMiddleware(http.HandlerFunc(protectedHandler)))
    http.ListenAndServe(":8080", mux)
}

优势:

  • 完全遵循依赖倒置,依赖抽象而非具体实现,便于单元测试(可传入Mock Repository)
  • 无全局状态,不同Middleware实例可绑定不同的Repository实现
  • 代码简洁直观,契合Go的函数式编程风格

2. 结构体封装Middleware(适合复杂场景)

如果Middleware需要多个依赖(比如同时需要Repository和配置服务),可以把Middleware封装为结构体,将依赖作为结构体字段注入。

示例代码:

// 封装Middleware的结构体,包含所需依赖
type AuthMiddleware struct {
    userRepo UserRepository
    config   *AppConfig
}

// 构造函数,注入所有依赖
func NewAuthMiddleware(repo UserRepository, config *AppConfig) *AuthMiddleware {
    return &AuthMiddleware{
        userRepo: repo,
        config:   config,
    }
}

// 实现Middleware逻辑
func (am *AuthMiddleware) Wrap(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        // 使用结构体中的依赖执行认证逻辑
        user, err := am.userRepo.FindByToken(token)
        if err != nil || user == nil {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }

        ctx := context.WithValue(r.Context(), "authUser", user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// 使用示例
func main() {
    userRepo := NewPostgresUserRepository()
    config := LoadAppConfig()
    authMiddleware := NewAuthMiddleware(userRepo, config).Wrap

    mux := http.NewServeMux()
    mux.Handle("/protected", authMiddleware(http.HandlerFunc(protectedHandler)))
    http.ListenAndServe(":8080", mux)
}

优势:

  • 适合依赖较多的场景,结构体可清晰管理多个依赖项
  • 便于后续扩展,新增依赖只需在结构体中添加字段即可

关键注意事项

  • 依赖抽象而非具体实现:始终定义Repository/Service的接口,不要直接依赖*PostgresUserRepository这类具体实现,这样测试时可轻松替换为Mock,提升代码可维护性。
  • 禁止全局变量传递依赖:全局变量会导致测试困难,且无法支持多实例场景(比如不同请求使用不同数据库连接)。
  • 用上下文传递认证结果:认证通过后,将用户信息存入请求的context,而非自定义结构体,后续Handler可统一通过context获取用户信息,符合Go标准实践。
  • 测试优先:注入依赖的方式让你能在单元测试中传入Mock Repository,模拟token无效、用户不存在等场景,无需启动真实数据库。

反模式:需要避免的做法

  • 直接在Middleware中初始化Repository实例:会导致Middleware与具体实现强耦合,无法替换,测试难度大。
  • 使用全局变量传递依赖:全局变量会让代码追踪困难,且无法支持并发安全的多实例场景。

内容的提问来源于stack exchange,提问作者Mateen Bagheri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 20:43:35