domain包出现sql.DB或redis.Client即非六边形架构,因领域层严禁依赖任何基础设施包,接口必须定义在domain/ports并由adapter实现,wire需在adapter层声明且按infra→adapter→domain顺序注入。

domain 包里出现 sql.DB 或 redis.Client 就不是六边形架构
Go 里没有“六边形框架”,只有依赖方向是否守得住。第一道红线就是 domain 层绝对不能 import 任何基础设施包,比如 "database/sql"、"github.com/go-redis/redis/v9"、"gorm.io/gorm"。一旦出现,后续换数据库、加缓存、写单元测试都会卡住。
常见错误现象:
-
domain/order_service.go直接接收*sql.DB作为参数 -
domain/user.go里调用了redis.NewClient() - 测试时发现没法 mock 仓储,因为实现和接口定义混在同一个包里
正确做法是:把所有交互契约抽成接口,放在 domain/ports(或 domain/repository)下,例如:
type UserRepository interface {
Save(ctx context.Context, u *User) error
FindByID(ctx context.Context, id string) (*User, error)
}
这个接口必须由 domain 包声明并导出,且该包不能 import 任何 infra 或 adapter 路径。
立即学习“go语言免费学习笔记(深入)”;
adapter/repository 实现接口时,只许用 infra 组件,不许塞业务逻辑
适配器不是“业务实现层”,而是“翻译层”:把领域接口的调用,转成具体技术操作。它能 import infra(如 "github.com/jmoiron/sqlx"),但不能 export 领域模型以外的类型,也不能做校验、转换、编排。
关键约束:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 结构体字段只能是 infra 类型,比如
db *sqlx.DB、client *kafka.Producer - 方法签名必须严格匹配 domain 接口,返回值类型不能改(比如不能把
*User换成userPo) - 数据映射(如
userPo → domain.User)应放在 adapter/repository 内部,不要泄露到 infra 层 - 别让 GORM 的
gorm.Model或gorm.DB出现在 domain 接口方法签名里
示例中 orderRepo 只持有一个 *sqlx.DB,Save 方法里只做 Exec + error 处理,不构造响应、不触发事件、不查关联表。
wire 的 InitializeApp() 必须落在 adapter 层,不能放在 domain 或 infra
Wire 不是自动构架工具,它是依赖流向的“公证人”。它的 provider 函数必须按 infra → adapter → domain 顺序声明,且整个 wire.go 文件必须位于能同时看到 infra 和 domain 的位置——也就是 adapter 层(如 cmd/app/wire.go 或 adapter/server/wire.go)。
如果放在 domain/wire.go,就会导致 domain 主动 new 出 mysqlRepo,违反依赖倒置;如果放在 infra/wire.go,又会让 infra 知道 domain 接口,形成反向引用。
典型错误:
-
domain/service.go里直接调用newMySQLUserRepository(db) -
wire.go在internal/下,生成的依赖图绕过端口约束 - provider 返回
*mysqlRepo而非UserRepository接口,导致调用方强依赖实现
gRPC 和 HTTP 都是驱动适配器,不是领域延伸
gRPC 不是“更高级的 DAO”,HTTP 也不是“UI 层”。它们都是外部系统驱动你业务的通道,属于输入端口的适配器,必须和领域逻辑物理隔离。
容易被忽略的点:
- proto 生成的
Ordermessage 不能当 domain 实体用,否则序列化规则会污染业务逻辑 - controller 或 gRPC handler 层负责把 request 映射为 domain.DTO,再交给 application service,不能跳过这层转换
- gRPC server 的启动代码(
grpc.NewServer()、RegisterXxxServer())必须放在adapter/server/grpc,不能进 domain - 不要在 domain 层 import
google.golang.org/grpc或github.com/gin-gonic/gin
真正难的不是写接口,而是每次新增一个外部依赖(比如加个短信网关),都要问一句:这个 type 是不是已经出现在 domain 包里了?

















