
本文介绍一种基于「中介者模式」的 go 项目结构重构方案,通过引入独立的 brokers 包解耦主路由与业务子包,彻底消除循环依赖,同时提升可测试性与可维护性。
本文介绍一种基于「中介者模式」的 go 项目结构重构方案,通过引入独立的 brokers 包解耦主路由与业务子包,彻底消除循环依赖,同时提升可测试性与可维护性。
在使用 Gorilla Mux 构建分层 Web 服务时,常见的项目结构(如 main_package 下嵌套 child_package1/2/3)极易引发循环导入:主包需注册子包的 Handler 函数,而子包单元测试又需调用主包的 Handlers() 创建测试服务器——导致 import "main_package" 与 import "child_packageX" 相互引用,Go 编译器直接报错。
根本解法不是妥协于 init() 或 _test.go 黑技巧,而是重构依赖流向:让所有跨包通信经由一个中立、无业务逻辑的 brokers 包统一调度,实现“主包不直连子包,子包不感知主包”。
✅ 推荐项目结构(扁平化 + 职责分离)
project/ ├── main_package/ # 入口 & 路由组装(仅依赖 brokers) ├── brokers/ # 核心中介:路由 URI → 子包 Delegate 的映射工厂 ├── child_package1/ # 纯业务逻辑:暴露 Delegate 实现 MyHandler ├── child_package2/ └── child_package3/
⚠️ 关键原则:main_package 不 import 任何 child_packageX;child_packageX 不 import main_package 或 brokers(仅需 net/http)。
✅ brokers 包:轻量级路由分发器
brokers/brokers.go 定义统一接口与工厂函数:
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
package brokers
import (
"net/http"
"path/to/child_package1"
"path/to/child_package2"
"path/to/child_package3"
)
type Broker interface {
MyHandler(http.ResponseWriter, *http.Request)
}
// New 根据 URI 返回对应子包的 Delegate 实例
func New(uri string) Broker {
switch uri {
case "/api1":
return &child_package1.Delegate{}
case "/api2":
return &child_package2.Delegate{}
case "/api3":
return &child_package3.Delegate{}
default:
return &dummyBroker{} // 可选:返回 404 处理器
}
}
// dummyBroker 实现空处理(避免 nil panic)
type dummyBroker struct{}
func (d *dummyBroker) MyHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Not Found", http.StatusNotFound)
}
✅ 子包:专注业务,零外部依赖
每个 child_packageX 仅需实现 Broker 接口,无需知道路由或数据库:
// child_package1/child_package1.go
package child_package1
import (
"encoding/json"
"net/http"
)
type Delegate struct {
// 可按需注入依赖(如 db、config),但绝不硬编码 import
}
func (d *Delegate) MyHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"msg": "from api1"})
}
✅ 主包:声明式路由组装
main_package/main_package.go 仅依赖 brokers 和 mux:
package main_package
import (
"database/sql"
"github.com/gorilla/mux"
"net/http"
"path/to/brokers"
)
func Handlers(db *sql.DB, customerUploadFile string) *mux.Router {
router := mux.NewRouter()
// 路由与业务完全解耦:URI → Broker 实例
router.HandleFunc("/api1", brokers.New("/api1").MyHandler)
router.HandleFunc("/api2", brokers.New("/api2").MyHandler)
router.HandleFunc("/api3", brokers.New("/api3").MyHandler)
// 静态文件路由(保持原逻辑)
fileHandler := http.FileServer(http.Dir("./client/compiled"))
router.PathPrefix("/").Handler(http.StripPrefix("/", fileHandler))
return router
}
✅ 单元测试:精准验证,无循环依赖
- 测试路由装配:仅 import main_package 和 brokers,无需子包
- 测试业务逻辑:直接 new 子包 Delegate,调用 MyHandler
- 测试端到端:用 httptest 启动 Handlers(),验证 HTTP 响应
// test/broker_test.go
package test
import (
"net/http"
"net/http/httptest"
"testing"
"path/to/brokers"
)
func TestAPI1ReturnsJSON(t *testing.T) {
req := httptest.NewRequest("GET", "/api1", nil)
w := httptest.NewRecorder()
brokers.New("/api1").MyHandler(w, req) // 直接测试 Delegate
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
if w.Header().Get("Content-Type") != "application/json" {
t.Fatal("missing JSON header")
}
}
✅ 总结:为什么这是最佳实践?
- 零循环依赖:依赖图变为单向链 main_package → brokers → child_packageX
- 高可测性:子包可独立测试(无 HTTP server 依赖),主包可验证路由绑定逻辑
- 易扩展:新增 API 只需在 brokers.New() 中添加 case,无需修改主包 import 列表
- 职责清晰:主包管“怎么路由”,子包管“做什么”,broker 管“谁来响应”
? 进阶提示:可将 Delegate 设计为接收依赖的构造函数(如 NewDelegate(db *sql.DB)),再通过 brokers 的工厂注入,实现依赖注入(DI)风格,进一步解耦数据访问层。










