2. Go 语言项目结构基础:标准 Go 项目布局与 DDD 准备

在云原生与微服务架构中,项目结构不仅是代码的物理组织方式,更是团队协作、部署策略和领域建模的基石。本章将深入剖析 Standard Go Project Layout 的核心原则,明确 cmd、internal、pkg 三大目录的职责边界,并在此基础上为引入领域驱动设计(DDD)做好结构准备。

2.1 标准 Go 项目布局全景

Go 官方并未强制规定项目布局,但社区经过多年实践形成了广泛认可的 Standard Go Project Layout。其核心思想是:按职责分层,按边界隔离。以下是一个典型的生产级项目骨架:

my-go-project/
├── cmd/                    # 应用入口(main 包)
│   ├── api/                # HTTP/gRPC 服务入口
│   │   └── main.go
│   └── worker/             # 后台任务入口
│       └── main.go
├── internal/               # 私有应用逻辑(禁止外部导入)
│   ├── domain/             # 领域模型与业务规则
│   ├── service/            # 应用服务(用例编排)
│   └── repository/         # 数据持久化实现
├── pkg/                    # 可复用的公共库
│   ├── logger/             # 日志封装
│   └── config/             # 配置加载
├── api/                    # API 定义(proto / OpenAPI)
├── configs/                # 配置文件模板
├── scripts/                # 构建与部署脚本
├── test/                   # 外部集成测试
├── go.mod
└── go.sum
关键原则: cmd 只做依赖注入和启动编排,不包含业务逻辑;internal 是应用的核心堡垒,禁止外部包直接引用;pkg 是经过抽象的可复用工具,应保持无业务语义。

2.2 三大核心目录的职责划分

目录 职责 导入限制 典型内容
cmd/ 应用入口点,每个子目录对应一个可执行二进制 只能导入 internal 和 pkg main() 函数、依赖注入、服务器启动
internal/ 私有应用逻辑,Go 编译器强制禁止外部导入 仅限本项目内部使用 领域模型、用例、仓储实现、中间件
pkg/ 可对外共享的通用库,无业务上下文 可被外部项目导入 日志、配置、HTTP 客户端、工具函数
常见陷阱: 将业务逻辑直接写在 cmd/ 的 main.go 中,或将领域模型放在 pkg/ 下导致外部依赖泄漏。记住:cmd 是薄薄的胶水层,pkg 不应包含任何业务实体。

2.3 从分层到领域:为 DDD 做准备

标准 Go 项目布局天然支持 分层架构,但要演进到 DDD,需要在 internal/ 内部进一步划分领域边界。下图展示了从传统分层到 DDD 四层架构的映射关系:

从标准布局到 DDD 四层架构映射 标准 Go 项目布局 cmd/ 应用入口 / 依赖注入 internal/service/ 应用服务 / 用例编排 internal/domain/ 领域模型 / 业务规则 internal/repository/ DDD 四层架构 用户接口层 Handler / Controller 应用层 Application Service / DTO 领域层 Entity / ValueObject / DomainService 基础设施层 💡 标准布局的 internal/domain 直接对应 DDD 领域层,是业务核心所在

2.4 internal/domain:领域模型的核心阵地

在 DDD 准备阶段,internal/domain 是最重要的目录。它应该包含:

  • 实体(Entity):具有唯一标识的业务对象,如 User、Order
  • 值对象(Value Object):不可变、无标识的业务概念,如 Money、Address
  • 领域服务(Domain Service):跨多个实体或值对象的业务逻辑
  • 仓储接口(Repository Interface):定义持久化契约,不包含实现
  • 领域事件(Domain Event):业务状态变更的声明
实践要点: domain 层应零外部依赖——不导入任何框架、数据库驱动或 HTTP 库。它只依赖 Go 标准库和自身定义的接口。这是 DDD 架构的“黄金法则”。

2.5 internal/service:应用服务的编排层

internal/service 对应 DDD 的 应用层,负责:

  1. 接收来自 cmd/ 或 handler 的请求
  2. 调用 domain 层的实体和领域服务执行业务规则
  3. 通过 repository 接口持久化数据
  4. 返回结果或 DTO(数据传输对象)

一个典型的应用服务示例:

// internal/service/order_service.go
package service

import (
    "context"
    "my-project/internal/domain"
)

type OrderService struct {
    orderRepo domain.OrderRepository
    paymentSvc domain.PaymentService
}

func NewOrderService(repo domain.OrderRepository, ps domain.PaymentService) *OrderService {
    return &OrderService{orderRepo: repo, paymentSvc: ps}
}

func (s *OrderService) CreateOrder(ctx context.Context, userID string, items []domain.OrderItem) (*domain.Order, error) {
    // 1. 领域层创建订单实体
    order, err := domain.NewOrder(userID, items)
    if err != nil {
        return nil, err
    }
    // 2. 调用领域服务处理支付
    if err := s.paymentSvc.Process(ctx, order); err != nil {
        return nil, err
    }
    // 3. 仓储持久化
    if err := s.orderRepo.Save(ctx, order); err != nil {
        return nil, err
    }
    return order, nil
}

2.6 pkg/ 与 internal/ 的边界决策

很多团队在 pkg/ 和 internal/ 之间犹豫不决。以下决策矩阵可帮助判断:

判断条件 放入 pkg/ 放入 internal/
是否包含业务语义 否(如日志、配置、HTTP 客户端) 是(如订单、用户、支付模型)
是否被外部项目复用 是(需保持向后兼容) 否(仅限本项目)
变更频率 低(稳定接口) 高(随业务迭代)
测试策略 单元测试为主 单元 + 集成测试

2.7 本章小结:结构即架构

标准 Go 项目布局不是教条,而是经过验证的 架构边界表达方式。通过 cmd/internal/pkg 的清晰划分,我们为后续的 DDD 演进、容器化部署和 CI/CD 流水线奠定了坚实的结构基础。记住:好的项目结构让领域边界可见,让部署单元可独立演进。

下一步行动: 在开始编码前,先画出你的领域边界图,然后按照 cmd → internal/domain → internal/service → internal/repository 的顺序创建目录结构。让结构驱动实现,而非让实现随意生长。