本文给出一个落地可执行的 Go DDD 代码组织方案。包含分层含义、推荐目录树、包命名与文件命名规范、典型代码片段领域实体、仓储接口、应用服务、适配器/实现、防坑注意项与代码风格建议。侧重 最小侵入、依赖倒置、包循环避免 与 Go 风格gofmt、小写包名、单一职责、内建 internal 与 pkg 约束。1. 基本原则与 Go 的契合点领域优先、基础设施靠后领域模型domain不依赖任何数据库、Web、框架或第三方库。依赖倒置高层application / adapters依赖领域接口domain interfaces具体实现注入到运行时。包的最小化与单一职责一个包只聚焦一组职责例如 order 聚合、user 聚合避免出现“god package”。使用 internal 隔离实现细节对外暴露只放在 pkg 或根导出包其他实现放 internal。避免循环依赖通过接口与构造函数解耦或把共享类型放到 pkg谨慎或 shared尽量少用。2. 推荐目录结构示例myapp/ ├── cmd/ │ └── myapp/ # 程序入口main.go——可多个 cmd │ └── main.go ├── configs/ # 配置文件、模板 ├── internal/ │ ├── domain/ # 领域层只含接口、实体、领域服务、事件 │ │ ├── user/ │ │ │ ├── entity.go │ │ │ ├── value_objects.go │ │ │ └── repository.go # 仓储接口 │ │ └── order/ │ │ └── ... │ ├── application/ # 应用层用例/事务协调、DTO、服务 │ │ └── usercase/ │ │ ├── service.go │ │ └── dto.go │ ├── adapter/ # 适配器输入/输出实现 │ │ ├── http/ │ │ │ └── user_handler.go │ │ └── grpc/ │ └── infra/ # 基础设施DB、缓存、邮件、外部系统 │ ├── db/ │ │ └── postgres_user_repo.go │ └── logger/ ├── api/ # OpenAPI/Protos/接口定义 ├── pkg/ # 可以被外部项目复用的包小心使用 ├── scripts/ ├── deployments/ └── go.mod说明把业务代码放 internal确保不会被外部 import增强封装。domain 只包含领域概念与仓储接口不包含任何 ORM 或 HTTP 相关内容。adapter 或 infra 放置实现仓储实现、HTTP handler、消息总线消费者等。3. 包命名与文件命名规范包名短小、单数、小写例如 user, order, payment不要下划线或驼峰。避免使用 domain 下的通用包名如 models、common—这些通常招致职责不清。文件名按职责细分entity.go, repository.go, service.go, handler_http.go避免单文件过大。类型命名导出类型使用大写User, Order非导出使用小写。接口命名接口名建议用行为后缀UserRepository, PaymentGateway而不是 IUser 风格。变量/函数命名驼峰小写导出函数首字母大写。4. 领域层internal/domain/...特点纯净、无依赖实现细节。示例internal/domain/user/entity.gopackage user import time // User 是聚合根 type User struct { ID string Email string Password string // 哈希 CreatedAt time.Time } func NewUser(id, email, passwordHash string) *User { return User{ID: id, Email: email, Password: passwordHash, CreatedAt: time.Now()} } func (u *User) ChangeEmail(new string) { // 领域验证/不变式在此处实现 u.Email new }示例internal/domain/user/repository.gopackage user // UserRepository 定义了持久化契约接口 type UserRepository interface { Save(u *User) error FindByID(id string) (*User, error) FindByEmail(email string) (*User, error) }说明领域层定义契约接口但不包含实现。实现应在 internal/infra 或 internal/adapter/persistence。5. 应用层internal/application/...职责编排应用用例、组织事务、调用领域接口、转换 DTO。示例internal/application/usercase/service.gopackage usercase import ( errors myapp/internal/domain/user ) type UserService struct { repo user.UserRepository } func NewUserService(r user.UserRepository) *UserService { return UserService{repo: r} } func (s *UserService) Register(email, passwordHash string) (*user.User, error) { // 1. 领域规则校验可调用领域服务 if existing, _ : s.repo.FindByEmail(email); existing ! nil { return nil, errors.New(email exists) } u : user.NewUser(generateID(), email, passwordHash) if err : s.repo.Save(u); err ! nil { return nil, err } return u, nil }注意应用层可以处理事务边界例如开始/提交 DB 事务但应保持尽可能薄。6. 适配器/基础设施层internal/adapter, internal/infra职责把领域接口连接到具体技术实现ORM 、HTTP、消息队列。示例internal/infra/db/postgres_user_repo.gopackage db import ( database/sql myapp/internal/domain/user ) type PostgresUserRepo struct { db *sql.DB } func NewPostgresUserRepo(db *sql.DB) *PostgresUserRepo { return PostgresUserRepo{db: db} } func (r *PostgresUserRepo) Save(u *user.User) error { // SQL 操作 return nil } func (r *PostgresUserRepo) FindByID(id string) (*user.User, error) { return nil, nil } func (r *PostgresUserRepo) FindByEmail(email string) (*user.User, error) { return nil, nil }在cmd/myapp/main.go中构建依赖关系 并注入func main() { db : mustOpenDB() userRepo : db.NewPostgresUserRepo(dbConn) userSvc : usercase.NewUserService(userRepo) // wire up http handlers with userSvc }7. 关于包名 user.User 的讨论你可能注意到在 Go 里 user.User包名 类型名经常出现。这是正常且符合惯例的。它能清晰表达类型 User 属于 user 包。但要注意若觉得冗余可以在导入时起别名u myapp/internal/domain/user使用 u.User。不推荐把包也命名为 models、entities 之类通用名这会降低可读性。建议按 聚合aggregate建包domain/user、domain/order更契合 DDD。8. 事务与一致性边界短事务在应用层开启并提交例如在 service 方法内把事务对象通过仓储实现传递。跨聚合事务优先用最终一致性事件/消息而不是分布式事务两阶段除非必须。样例在 application 层开始事务// pseudo func (s *Service) DoSomething(...) error { tx : db.Begin() defer func() { if err ! nil { tx.Rollback() } else { tx.Commit() } }() // repo 使用 tx 实现 }9. 测试建议领域单测只针对 internal/domain/* 的行为和不变式编写单元测试无需 DB。集成测试把 infra 的实现替换为 test doubles 或使用测试 DBdocker-compose/testcontainers。端到端测试在 CI 中用真实依赖容器化 DB / MQ跑 e2e 测试。10. 常见反模式与注意事项把 ORM 代码放进 domain违反依赖倒置。领域应该只关心接口。大而全的 models 包职责模糊容易形成循环依赖。全局状态/单例数据库变量不利于测试与并发。大量使用 pkg/shared导致隐性耦合优先用明确的接口注入。忽略错误包装在边界处用 errors.Wrap 或 %w 链接错误上下文便于排查。11. 代码风格 工具链建议gofmt / goimports / golangci-lint开启 govet,staticcheck。采用 go test ./... 在 CI 中持续运行。使用 -race 在集成测试中检查竞态。依赖管理用 go mod避免把 vendor 弹性化管理滥用。12. 实战示例完整包关系小结internal/domain/user实体、值对象、仓储接口、领域服务。internal/application/usercase应用服务、DTO、用例编排。internal/infra/dbPostgres/MySQL 的仓储实现。internal/adapter/httpHTTP 层处理 request - DTO - 调用 application service返回标准化错误/响应。这样可以保证领域不依赖 infrainfra 依赖 domainapplication 把 domain 与 infra 组装起来。13. 最后一张清单落地前检查领域层无任何第三方依赖。仓储接口放领域具体实现放 infra/adapter。使用 internal 隔离实现暴露可复用组件放 pkg。包名小写、单数、职责单一。主函数cmd/负责依赖注入与启动。CI 覆盖 gofmt, golangci-lint, go test。结语把 DDD 的思路带入 Go 项目不是为了机械搬运概念而是把“边界明确、职责单一、可测试、易演进”的工程实践落地。用 internal、短小清晰的包名、接口注入与慎用共享包就能既保留 Go 的简洁又实现领域驱动的长期可维护性。