六边形架构在Go中通过编译期导入约束保障分层:domain/禁止引入任何infra依赖,必须仅含纯业务逻辑;ports定义接口,adapter实现但不暴露技术类型;wire按infra→adapter→application顺序注入确保依赖收敛。

六边形架构在 Go 里不是靠模板生成的,而是靠每次 go build 时包导入是否报错来验证的——只要 domain/ 目录下出现 github.com/go-sqlite/sqlite3 或 google.golang.org/grpc,就说明领域层已被技术细节污染,DDD 实践已经失效。
domain 包里为什么不能 import 任何 infra 类型
这是六边形架构最硬的边界:领域层必须是纯业务逻辑容器,不感知数据库、HTTP、消息队列等任何外部机制。一旦 domain/order.go 出现 *sql.DB 或 redis.Client,后续换 PostgreSQL、加 Redis 缓存、切到 gRPC 协议,都得改业务代码——这直接违背 DDD “稳定核心、可变外设”的原则。
常见错误现象:
-
domain/service.go直接调用db.QueryRowContext() -
domain/entity.go带gorm.Model或json:"id"标签 - 测试时发现无法 mock 仓储,因为接口和实现混在同一个包
正确做法:在 domain/ports 下定义接口,例如:
立即学习“go语言免费学习笔记(深入)”;
type OrderRepository interface {
Save(ctx context.Context, o *Order) error
FindByID(ctx context.Context, id int) (*Order, error)
}
这个接口只能依赖 context.Context 和 domain.Order,不能引入任何第三方类型。
adapter/repository 怎么写才不算越界
适配器是“翻译官”,不是“实现者”。它的唯一任务是把 domain/ports 中定义的接口调用,转成具体技术操作。它可 import github.com/jmoiron/sqlx,但绝不能 export sqlx.DB 或任何 infra 类型给上层。
关键约束:
- 结构体字段只放基础设施对象,比如
db *sqlx.DB,不放业务字段 - 方法签名必须 1:1 匹配
domain/ports.OrderRepository,包括参数类型和返回值 - 不做数据转换:不把
map[string]interface{}转成domain.Order,那是adapter/controller或infra/mapper的事 - 不暴露 ORM 实体(如 GORM 的
gorm.Model),避免 domain 层被序列化规则绑架
wire 注入顺序错,整个架构就塌一半
wire 不保证架构正确,但它能立刻暴露依赖错误。如果 provider 声明顺序是 app.NewOrderService → postgres.NewOrderRepo → sqlx.NewDB,wire 就会报 unresolved dependency;只有按 infra → adapter → application 顺序声明,才能确保依赖流向从外向内收敛。
典型 wire.go 片段:
func InitializeApp() (*App, error) {
wire.Build(
sqlx.NewDB, // infra
postgres.NewOrderRepo, // adapter
app.NewOrderService, // application
handler.NewOrderHandler, // adapter (HTTP/gRPC)
)
return &App{}, nil
}
注意点:
-
InitializeApp()必须放在cmd/或adapter/server/下,不能放在domain/或app/里 - 所有 service 构造函数参数必须是接口,比如
func NewOrderService(repo domain.OrderRepository),而非*postgres.orderRepo - handler 层不能 new service,必须由 wire 注入已构造好的实例
gRPC 和 HTTP handler 怎么当好“驱动适配器”
gRPC 不是领域模型的延伸,它是外部系统驱动你业务的一条通道。proto 文件里的 OrderRequest 是传输契约,不是领域实体。把它当 domain.Order 用,等于让序列化规则决定业务规则。
正确分层:
-
adapter/grpc/order_service.go解析pb.CreateOrderRequest→ 转成domain.CreateOrderRequest(DTO)→ 调用app.OrderService.Create() -
domain/下的Order必须是充血模型:含校验逻辑、状态流转方法,不含 JSON 或 DB 标签 - 不要在
adapter/grpc/里做领域规则判断(比如“金额不能为负”),那属于domain.Order的方法职责
最容易被忽略的一点:目录隔离是物理防线,不是装饰。哪怕只多一个 import "database/sql" 在 domain/ 下,六边形就塌了——这不是风格问题,是编译期可验证的架构事实。



















