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
相关产品推荐
相关产品推荐

