3、耦合的根源:领域层对基础设施层的直接依赖、硬编码依赖的坏味道

在领域驱动设计的实践中,领域层是系统的核心与灵魂,它承载着业务规则与逻辑。然而,当领域层直接依赖基础设施层(如数据库、外部服务、文件系统)时,这种核心地位便会被侵蚀。这种依赖是软件系统中耦合的主要根源之一,它让领域逻辑变得脆弱、难以测试且无法独立演化。

3.1 直接依赖:领域层与基础设施层的“硬连接”

当领域层中的实体或服务直接实例化一个数据库仓库或发送HTTP请求时,领域层便与基础设施层产生了“硬连接”。这种依赖关系破坏了领域层的纯净性,使得任何基础设施的变更都可能引发领域层的连锁修改。

典型坏味道: 在领域服务中直接使用 sql.DB 或 *gorm.DB 对象进行数据查询,或在领域实体中直接调用第三方API。

下面的SVG图展示了这种不合理的依赖关系:领域层(核心)直接指向基础设施层,形成了单向但有害的耦合。

图3-1:领域层对基础设施层的直接依赖(耦合根源) 领域层(Domain) 业务逻辑 · 实体 · 值对象 基础设施层(Infrastructure) 数据库 · 外部服务 · 文件系统 直接依赖(有害) 问题:领域层无法独立于基础设施层进行测试与演化,任何数据库或服务的变更都会波及核心业务逻辑。

3.2 硬编码依赖的四种典型坏味道

硬编码依赖是直接依赖的具体表现形式,它们在代码中留下了明显的“坏味道”。识别这些坏味道是重构的第一步。下表总结了四种最常见的场景及其危害。

坏味道类型 代码表现 核心危害
1. 直接实例化 在领域服务中使用 NewMySQLRepository() 领域层与具体数据库实现绑定,无法切换存储
2. 静态方法调用 调用 EmailSender.Send() 或 RedisCache.Get() 隐藏依赖关系,难以Mock,测试困难
3. 全局变量/单例 使用 var db *sql.DB 全局变量 隐式依赖,并发不安全,上下文混乱
4. 配置硬编码 在领域逻辑中直接写数据库连接字符串或API密钥 环境耦合,安全风险,无法多环境部署
警示: 硬编码依赖不仅导致耦合,还会让领域逻辑的单元测试变得极其困难。例如,测试一个包含直接数据库调用的订单服务,必须搭建真实的数据库环境,这违背了“测试应快速且独立”的原则。

3.3 耦合的连锁反应:从代码到架构

领域层与基础设施层的直接依赖并非孤立问题,它会引发一系列架构层面的连锁反应,最终导致整个系统的僵化。

  • 可测试性崩塌: 领域逻辑无法脱离基础设施单独测试,单元测试退化为集成测试,运行缓慢且不稳定。
  • 可维护性下降: 更换数据库或消息队列时,需要修改领域层代码,增加了回归风险。
  • 业务逻辑污染: 基础设施的异常处理、连接管理、序列化细节侵入领域层,使业务规则变得模糊。
  • 团队协作阻塞: 领域专家与基础设施开发者的工作被强行绑定,无法并行推进。

下面的SVG图展示了这种耦合如何从代码层面扩散到整个架构,形成“雪崩效应”。

图3-2:耦合的连锁反应——从代码到架构的雪崩效应 硬编码依赖 (耦合根源) 可测试性崩塌 单元测试 → 集成测试 可维护性下降 变更扩散 · 回归风险 业务逻辑污染 基础设施细节侵入 团队协作阻塞 无法并行开发 最终结果:系统僵化 · 难以演进 · 技术债务累积

3.4 代码层面的具体表现

让我们通过一段Go代码来直观感受硬编码依赖的坏味道。以下是一个典型的反面示例:

// 领域层 —— 订单服务(反面示例:直接依赖基础设施)
package domain

import (
    "database/sql"
    "fmt"
    "net/smtp"
)

type OrderService struct {
    db *sql.DB  // 直接依赖数据库
}

func (s *OrderService) CreateOrder(order Order) error {
    // 直接操作数据库
    _, err := s.db.Exec("INSERT INTO orders ...")
    if err != nil {
        return fmt.Errorf("failed to save order: %w", err)
    }

    // 直接发送邮件
    err = smtp.SendMail("smtp.example.com:25", nil, "from@example.com",
        []string{"user@example.com"}, []byte("Order created"))
    if err != nil {
        return fmt.Errorf("failed to send email: %w", err)
    }

    return nil
}
识别要点: 上述代码中,OrderService 直接使用了 *sql.DB 和 smtp.SendMail。这意味着:1) 无法在不连接真实数据库的情况下测试业务逻辑;2) 更换数据库或邮件服务商时必须修改领域层代码;3) 领域层被迫处理基础设施的异常类型。

3.5 解耦的起点:依赖倒置原则(DIP)

解决耦合问题的核心武器是依赖倒置原则(Dependency Inversion Principle, DIP)。该原则要求:

  1. 高层模块不应依赖低层模块,两者都应依赖抽象。
  2. 抽象不应依赖细节,细节应依赖抽象。

在DDD的语境下,这意味着领域层(高层模块)应定义接口(抽象),而基础设施层(低层模块)应实现这些接口。这样,依赖的方向就被反转了:领域层不再直接依赖基础设施,而是基础设施依赖领域层定义的接口。

关键转变: 从“领域层 → 基础设施层”的依赖,转变为“领域层 ← 基础设施层”的依赖。领域层成为依赖关系的控制者,而非被影响者。

下表对比了直接依赖与依赖倒置两种模式的核心差异:

维度 直接依赖(坏味道) 依赖倒置(解耦)
依赖方向 领域层 → 基础设施层 基础设施层 → 领域层(接口)
接口定义者 基础设施层或第三方库 领域层
测试方式 需要真实基础设施 可通过Mock实现接口
变更影响 基础设施变更波及领域层 领域层不受基础设施变更影响
领域纯净度 被基础设施细节污染 保持纯净,只关注业务