四、DDD四层架构详解:用户接口层、应用层、领域层、基础设施层的职责与依赖规则

领域驱动设计(DDD)的四层架构是构建复杂业务系统的核心骨架。它将代码按照职责划分为四个清晰的层次:用户接口层、应用层、领域层和基础设施层。理解每一层的职责以及它们之间的依赖规则,是进行有效限界上下文划分和模块设计的前提。

核心原则:依赖倒置(Dependency Inversion Principle, DIP)。高层模块(领域层)不依赖低层模块(基础设施层),两者都依赖抽象(接口)。这确保了领域层的纯粹性与可测试性。

4.1 四层架构总览与依赖方向

下图展示了DDD四层架构的标准分层及其依赖关系。箭头方向代表“依赖”方向,即“知道”或“调用”的方向。

DDD 四层架构与依赖规则 用户接口层 (User Interface) Controller / Handler / DTO / 视图 应用层 (Application) Application Service / DTO / 任务协调 领域层 (Domain) Entity / Value Object / Aggregate / Domain Service / Repository 接口 基础设施层 (Infrastructure) Repository 实现 / ORM / 消息队列 / 缓存 / 外部服务 依赖规则:外层依赖内层,内层不依赖外层。领域层是核心,零外部依赖。 基础设施层通过实现领域层定义的接口(Repository接口)来支持上层,体现依赖倒置。

4.2 各层职责详解

4.2.1 用户接口层 (User Interface Layer)

这是系统的对外窗口,负责处理与外部用户或系统的交互。在Go Web应用中,它通常表现为HTTP Handler、gRPC服务端或消息监听器。

  • 核心职责:请求解析、参数校验、响应渲染、会话管理。
  • 典型组件:Controller、Handler、Middleware、DTO(数据传输对象)、View(模板)。
  • 关键约束:不包含任何业务逻辑。它只负责将用户请求“翻译”成应用层能理解的指令,并将应用层的返回结果“翻译”成用户期望的格式。
Go 实践:在Gin或Echo框架中,Handler函数应保持精简。它们调用应用层服务,并处理HTTP状态码和JSON序列化。业务规则、数据校验(除基础格式外)不应出现在这里。

4.2.2 应用层 (Application Layer)

应用层是“薄”层,它不包含业务规则,而是负责协调领域对象来完成一个用户用例。它是用户接口层和领域层之间的桥梁。

  • 核心职责:事务管理、安全授权、任务调度、事件发布、领域对象编排。
  • 典型组件:Application Service、DTO、Event Publisher。
  • 关键约束:应用服务本身不包含复杂的业务判断。它获取领域对象(通过Repository接口),调用领域对象的方法,然后保存结果。它决定“什么时候做”,而领域层决定“怎么做”。
// 应用层服务示例
type OrderApplicationService struct {
    orderRepo    domain.OrderRepository
    productRepo  domain.ProductRepository
    eventBus     domain.EventBus
}

func (s *OrderApplicationService) PlaceOrder(ctx context.Context, cmd PlaceOrderCommand) error {
    // 1. 获取领域对象
    product, err := s.productRepo.FindByID(ctx, cmd.ProductID)
    if err != nil {
        return err
    }
    // 2. 委托领域对象执行业务逻辑
    order, err := domain.CreateOrder(cmd.UserID, product, cmd.Quantity)
    if err != nil {
        return err
    }
    // 3. 保存领域对象
    if err := s.orderRepo.Save(ctx, order); err != nil {
        return err
    }
    // 4. 发布领域事件
    s.eventBus.Publish(ctx, order.Events()...)
    return nil
}

4.2.3 领域层 (Domain Layer)

这是DDD的核心,承载了所有业务逻辑和业务规则。它是软件中最有价值、最稳定、最需要被保护的部分。

  • 核心职责:表达业务概念、执行业务规则、维护业务状态的一致性。
  • 典型组件:Entity、Value Object、Aggregate、Domain Service、Domain Event、Repository接口、Factory。
  • 关键约束:领域层是独立的,不依赖任何框架、数据库、外部服务或用户接口。它只依赖纯Go标准库和自身定义的接口。这种独立性使得领域层可以被单独测试和复用。
常见误区:将数据库ORM的注解(如GORM的Tag)或序列化标签写在领域实体上。这会污染领域层,使其与基础设施耦合。领域实体应该是纯粹的Go结构体,只包含业务属性和行为。

4.2.4 基础设施层 (Infrastructure Layer)

这一层为其他层提供技术支持,包括数据持久化、消息发送、文件存储、外部API调用等。它实现了领域层定义的接口。

  • 核心职责:技术实现、资源访问、第三方集成。
  • 典型组件:Repository实现(如GORM、SQLx)、消息队列客户端、缓存客户端(如Redis)、HTTP客户端。
  • 关键约束:基础设施层“向上”依赖领域层(实现其接口),而不是被领域层依赖。这种依赖倒置保证了领域层的纯洁性。
// 领域层定义的接口
package domain

type OrderRepository interface {
    Save(ctx context.Context, order *Order) error
    FindByID(ctx context.Context, id OrderID) (*Order, error)
}

// 基础设施层的实现
package infrastructure

type GormOrderRepository struct {
    db *gorm.DB
}

func (r *GormOrderRepository) Save(ctx context.Context, order *domain.Order) error {
    // 将领域对象转换为ORM模型并保存
    return r.db.WithContext(ctx).Create(order).Error
}

4.3 依赖规则与模块划分

严格的依赖规则是四层架构的基石。下表总结了各层之间的依赖关系与通信方式:

源层 目标层 依赖方式 说明
用户接口层 应用层 直接调用 注入应用层服务接口,调用其方法。
用户接口层 领域层 间接依赖 通常不直接依赖,但可以引用DTO或简单值对象。
应用层 领域层 直接调用 调用领域实体、领域服务、Repository接口。
应用层 基础设施层 依赖倒置 通过依赖注入获取基础设施层的实现。
领域层 基础设施层 禁止依赖 领域层完全不知道基础设施层的存在。
基础设施层 领域层 实现接口 实现领域层定义的Repository等接口。

4.4 在Go项目中组织四层代码

在Go项目中,我们通常按照限界上下文(Bounded Context)来组织顶层目录,在每个上下文内部再按照四层架构划分包。这是一种推荐的模块划分方式。

your-project/
├── internal/
│   ├── order/                    # 订单限界上下文
│   │   ├── interface/            # 用户接口层
│   │   │   └── http/
│   │   │       └── handler.go
│   │   ├── application/          # 应用层
│   │   │   └── service.go
│   │   ├── domain/               # 领域层
│   │   │   ├── entity.go
│   │   │   ├── value_object.go
│   │   │   ├── repository.go     # 接口定义
│   │   │   └── service.go
│   │   └── infrastructure/       # 基础设施层
│   │       ├── repository_impl.go
│   │       └── event_publisher.go
│   ├── product/                  # 产品限界上下文
│   │   └── ...
│   └── common/                   # 共享内核
│       └── ...
├── cmd/
│   └── server/
│       └── main.go               # 依赖注入与启动入口
└── go.mod
依赖注入(DI):在 main.go 或一个独立的 wire.go 文件中,我们实例化所有层的具体实现,并通过构造函数将它们注入到依赖它们的对象中。这是实现依赖倒置和保持各层解耦的关键技术。

通过严格遵守四层架构的职责与依赖规则,我们能够构建出高内聚、低耦合、易于测试和维护的Go Web应用。领域层作为业务核心,其稳定性和独立性是项目长期演进的基础。