ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

领域层不该直接连数据库:一个可运行的Go导入检查器

领域层不该直接连数据库:一个可运行的Go导入检查器

领域层里出现database/sql,并不一定马上引发事故,但它说明业务规则已经知道一种基础设施细节。以后换存储、做离线演算或写纯业务测试,都可能为这条依赖付额外成本。

检查这种边界,不必先引入完整架构平台。Go标准库的语法树已经能回答一个很具体的问题:指定目录内的Go文件,是否直接导入了不允许的包。

检查器只做它能证明的事

下面的程序扫描命令行传入的目录及子目录,读取导入声明,发现database/sql或net/http就返回失败。它跳过隐藏目录和vendor,语法错误同样失败。运行方式是go run checker.go ./internal/domain;无参数时用内嵌样本自检。

package main import ( "fmt" "go/parser" "go/token" "io/fs" "os" "path/filepath" "strconv" "strings" ) func forbidden(name string, src any) ([]string, error) { f, err := parser.ParseFile(token.NewFileSet(), name, src, 0) if err != nil { return nil, err } var bad []string for _, imp := range f.Imports { p, err := strconv.Unquote(imp.Path.Value) if err != nil { return nil, err } if p == "database/sql" || p == "net/http" { bad = append(bad, p) } } return bad, nil } func main() { if len(os.Args) == 1 { bad, err := forbidden("sample.go", "package domain; import _ \"database/sql\"") if err != nil || len(bad) != 1 { panic("self-check failed") } fmt.Println("import checker self-check: passed") return } failed := false err := filepath.WalkDir(os.Args[1], func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() && path != os.Args[1] && (d.Name() == "vendor" || strings.HasPrefix(d.Name(), ".")) { return filepath.SkipDir } if d.IsDir() || !strings.HasSuffix(path, ".go") { return nil } bad, err := forbidden(path, nil) if err != nil { return err } for _, p := range bad { fmt.Printf("%s: forbidden import %s\n", path, p); failed = true } return nil }) if err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(2) } if failed { os.Exit(1) } }

图中箭头表示运行时调用路径;代码依赖应由存储实现指向领域接口,不能按调用流反推import方向。

为什么不用文本搜索直接判定

语法树能避免把注释里的包名当成导入,也能处理别名导入;路径仍应按团队约定配置。这里故意只演示两个禁止项,真实项目可能还要限制内部基础设施包。

它也有清楚的盲区:这是语法扫描,不解析实际构建约束,不追踪传递依赖,不判断一个接口是否设计合理。生成代码和不同平台文件都可能被扫到。要回答“当前构建真正依赖哪些包”,应结合go list和类型信息,不能给此例贴上完整架构合规的标签。

10月16日Go团队介绍的gopls交互式重构,体现了更深的语义工具能力。自动改代码与守住架构边界可以配合,但编辑器能完成一次重构,不代表依赖政策也被执行。把小检查放入CI,先让规则稳定、错误提示可理解,再考虑扩大范围。

参考

  • Go parser标准库
  • Go官方:gopls交互式重构
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进