一、当建模变成炫技表演

有个现象特别有意思:很多团队刚开始接触领域驱动设计时,会把建模会议开成UML绘图大赛。我见过最夸张的案例是,某电商系统在商品模块设计了17个继承层次,包含"抽象商品基类"、"虚拟商品接口"、"可退货商品装饰器"等华丽结构。结果上线后发现,他们80%的业务场景只需要商品名称和价格两个字段。

// 过度设计的商品类示例(Java技术栈)
public abstract class AbstractProduct {
    private Long id;
    private String name;
    // 十余个抽象方法声明...
}

public interface DigitalProduct {
    void validateLicense();
    // 其他数字商品特有方法
}

public class RefundableProductDecorator extends Product {
    // 装饰器模式实现
    // 实际业务中退款逻辑其实就两行代码
}

这种过度建模就像给自行车装航天发动机,不仅浪费开发资源,更可怕的是会让业务方产生"这个系统很复杂"的认知负担。我曾参与改造过一个保险系统,他们把简单的保单状态流转做成了状态模式+策略模式的组合,结果每次业务规则调整都要修改5个类。

二、业务语义的失真表达

更隐蔽的问题是模型偏离业务语言。某银行项目里,业务人员说的"账户冻结",在代码里变成了"AccountStatusManagementService.changeState()"。这种术语断层会导致两个严重后果:

  1. 新成员需要做业务-代码的术语映射
  2. 业务规则变更时找不到对应代码位置
// 不良实践:技术术语与业务脱节
public class AccountService {
    public void updateAccountFlag(Long accountId, int flagCode) {
        // 方法参数用魔法数字表示业务状态
    }
}

// 改进后的领域模型
public class BankAccount {
    public void freeze(FreezeReason reason) {
        // 使用业务术语作为方法签名
    }
}

在物流系统项目中,我们通过事件风暴工作坊发现,业务人员说的"转运"包含三个明确阶段:分拣、干线运输、末端配送。但原有代码却把这些都塞进一个TransportService里。通过建立Sorter、LineHaul、LastMile等领域对象,不仅代码更清晰,还意外发现了业务流程中的优化点。

三、贫血模型的诱惑陷阱

很多团队会不自觉陷入贫血模型陷阱,比如这个订单处理的典型例子:

// 贫血模型示例(Java+Spring技术栈)
public class Order {
    // 只有属性和getter/setter
    private Long id;
    private String status;
    // ...其他字段
}

@Service
public class OrderService {
    @Transactional
    public void approveOrder(Long orderId) {
        Order order = orderRepository.findById(orderId);
        if ("CREATED".equals(order.getStatus())) {
            order.setStatus("APPROVED");
            // 数十行业务逻辑
        }
    }
}

这种写法的问题在于:

  1. 业务规则分散在服务层
  2. 领域对象没有行为能力
  3. 状态变更缺乏保护

我们改造后采用富领域模型:

public class Order {
    private OrderStatus status;
    
    public void approve() {
        if (!status.canApprove()) {
            throw new IllegalStateException();
        }
        this.status = OrderStatus.APPROVED;
        this.approvedAt = LocalDateTime.now();
    }
}

在电商秒杀系统实践中,将库存扣减逻辑从Service移到Product聚合根内部,不仅解决了并发问题,还使业务规则变更更可控。比如后来增加的区域库存限制,只需要修改Product类而不影响其他模块。

四、测试驱动的建模验证

如何判断模型是否合理?我的经验是用测试案例验证。某次金融项目中,我们通过测试用例发现了模型缺陷:

// 测试用例暴露模型问题(Java+JUnit5)
@Test
void should_reject_invalid_transfer() {
    Account from = new Account(1000);
    Account to = new Account(100);
    Money amount = new Money(2000);
    
    assertThrows(InsufficientBalanceException.class, 
        () -> from.transferTo(to, amount));
}

// 最初实现漏了资金冻结场景
@Test
void should_block_frozen_account() {
    Account account = new Account(1000);
    account.freeze();
    
    assertThrows(AccountFrozenException.class,
        () -> account.withdraw(500));
}

在医疗系统开发时,我们通过编写"患者出院时未结算禁止办理"的测试用例,发现了结算逻辑应该放在Patient聚合根而不是单独的BillingService中。这种测试优先的方法能有效避免过度工程。

五、持续演进的关键实践

好的领域模型需要持续打磨。在某SaaS平台项目中,我们建立了这些机制:

  1. 每月领域模型评审会,邀请业务方参与
  2. 代码中的@DomainStory注解标记业务背景
  3. 版本发布时同步更新领域词汇表
/**
 * @DomainStory 
 * 客户投诉处理流程的核心聚合
 * 业务规则:
 * - 紧急投诉需30分钟内响应
 * - 重复投诉自动升级
 */
public class Complaint {
    private List<FollowUp> histories;
    
    public void escalateIfNeeded() {
        // 业务规则实现
    }
}

物流跟踪系统通过事件溯源(Event Sourcing)技术,将业务人员的"包裹已到达分拣中心"这类陈述直接转化为领域事件,使模型始终保持与业务语言同步。

六、价值衡量的实用准则

最后分享几个判断模型价值的实用方法:

  1. 5分钟测试:能否在5分钟内给新人讲清楚核心领域?
  2. 变更成本评估:添加新业务规则要改多少层代码?
  3. 业务验证:模型是否解决了真实的业务痛点?

在零售库存系统改造中,我们通过将"安全库存"、"在途库存"等业务概念显式建模,使原本需要2周完成的月度报表开发缩短到3天。这才是领域驱动设计真正的价值体现。

记住,好的领域模型应该像精准的导航仪,而不是华丽的汽车模型。它不需要展示所有技术细节,但要确保团队不会在业务复杂度中迷路。当你在白板前画框图时,不妨问问:这个设计会让业务人员点头还是皱眉?这个问题的答案往往就是建模是否合理的试金石。