欢迎光临
我们一直在努力

领域驱动设计DDD 规约详解

一、什么是规约(Specification)?

规约(Specification)是领域驱动设计中的一种重要模式,属于战术设计层。它用来表达对象应该满足的业务规则或条件(即“什么是合格的”)。规约可以复用、组合,使复杂的业务规则易于表达、维护与测试。

简单来说:规约负责以可表达、可重用的对象形式表示企业的业务规则和标准。


二、规约的典型应用场景

  • 复杂的筛选条件 比如“所有状态为启用且余额大于1000的用户”。
  • 业务验证 比如“只能对未发货订单进行取消”。
  • 组合业务规则 e.g. 某操作必须满足多个条件,可以用规约组合这些条件。

  • 三、DDD中规约的结构

    通常,Specification模式包括:

    • 接口/抽象类:定义isSatisfiedBy方法,判断某对象是否满足规约。
    • 具体实现:针对某些具体业务条件实现isSatisfiedBy。
    • 规约组合:可以通过And、Or、Not等方式组合规约。

    典型接口定义如下(伪代码,更偏Java/C#风格):

    public interface Specification<T> {
    boolean isSatisfiedBy(T candidate);
    Specification<T> and(Specification<T> other);
    Specification<T> or(Specification<T> other);
    Specification<T> not();
    }


    四、规约实现举例

    以“用户”聚合为例,用Java仿写演示:

    public interface Specification<T> {
    boolean isSatisfiedBy(T candidate);

    default Specification<T> and(Specification<T> other) {
    return candidate -> this.isSatisfiedBy(candidate) && other.isSatisfiedBy(candidate);
    }

    default Specification<T> or(Specification<T> other) {
    return candidate -> this.isSatisfiedBy(candidate) || other.isSatisfiedBy(candidate);
    }

    default Specification<T> not() {
    return candidate -> !this.isSatisfiedBy(candidate);
    }
    }

    public class EnabledUserSpecification implements Specification<User> {
    @Override
    public boolean isSatisfiedBy(User user) {
    return user.isEnabled();
    }
    }

    public class RichUserSpecification implements Specification<User> {
    @Override
    public boolean isSatisfiedBy(User user) {
    return user.getBalance() > 1000;
    }
    }

    // 组合使用
    Specification<User> enabledAndRich = new EnabledUserSpecification().and(new RichUserSpecification());

    User u = …;
    if(enabledAndRich.isSatisfiedBy(u)){
    // 满足规约
    }


    五、规约在实际开发中的作用

  • 屏蔽复杂条件逻辑 通过组合、复用规约,解决if嵌套的“地狱”。
  • 业务语言表达能力增强 代码贴近业务,业务人员更容易理解。
  • 便于测试和演化 每个规约单一职责、易于测试,支持业务规则的灵活变更。
  • 与持久化、查询解耦 规约可用于内存判断,也可转换为数据库查询(比如JPA的Spec)。

  • 六、一些实用建议

    • 没有复杂规则,不必引入规约,保持简洁。
    • 可将规约作为领域服务的一部分,实现时关注业务含义;
    • 对于需要持久化的规约(如数据库查询),可用如JPA Specification等工具。

    七、参考资料

    • 《领域驱动设计:软件核心复杂性应对之道》(Eric Evans)
    • 《实现领域驱动设计》(Vaughn Vernon)
    • Specification Pattern (Martin Fowler)

    八、规约与领域模型、领域服务的关系

    在DDD中,业务规则可能分散在实体、值对象、聚合根和领域服务中。规约的职责与它们有交集也有区别:

    • 实体自身的简单规则(如有某个属性、非空校验),可以直接放在实体方法里。
    • 涉及多个对象或者复杂业务规则,适合用规约表达。
    • 规约本身可以是领域服务的一部分(如某些跨聚合的校验)。
    • 在聚合根保证一致性边界时,可以通过规约辅助判断修改是否合法。

    九、业务用例示例(更贴近真实开发)

    假设我们有一个电商系统,订单需要满足“可取消”这一业务逻辑:

    1. 可取消规约(OrderCanBeCanceledSpecification)

    领域专家对规则的描述
    • 未支付的订单可随时取消
    • 已支付、未发货订单可取消
    • 一旦发货则不可取消
    • 已完成或已关闭订单不可取消
    代码实现(以Java、C#为例)

    public class OrderCanBeCanceledSpecification implements Specification<Order> {
    @Override
    public boolean isSatisfiedBy(Order order) {
    if (order.getStatus() == OrderStatus.CLOSED || order.getStatus() == OrderStatus.COMPLETED) {
    return false;
    }
    if (order.getStatus() == OrderStatus.SHIPPED) {
    return false;
    }
    return true; // PENDING(未支付)或 PAID(已支付未发货)可以取消
    }
    }

    应用方式

    Order order = …;
    Specification<Order> canCancel = new OrderCanBeCanceledSpecification();
    if (canCancel.isSatisfiedBy(order)) {
    order.cancel();
    } else {
    throw new BusinessException("订单不可取消!");
    }


    十、规约的组合与复用

    假设你在系统中有越来越多的业务规则。引入规约后,你可以通过组合,实现“与”“或”“非”等复合逻辑。

    组合示例

    Specification<Order> paid = order -> order.isPaid();
    Specification<Order> notShipped = order -> !order.isShipped();

    Specification<Order> canRefund = paid.and(notShipped);

    if (canRefund.isSatisfiedBy(order)) {
    order.refund();
    }

    这样,假如你的规则变了,“已支付且未完成的订单可退款”,只需组合不同的规约。


    十一、规约的持久化扩展(数据库查询规约)

    在领域层判断规约往往是针对内存对象,但有场景需要用规约构造数据库查询(如JPA Specification、EF Core Specification等)。

    以JPA为例

    public class EnabledUserSpecification implements Specification<User> {
    public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
    return cb.equal(root.get("enabled"), true);
    }
    }

    你可以将领域规约转化为数据库查询规约,实现业务规则与持久层规则的解耦与统一。


    十二、其他语言示例(Python)

    class Specification:
    def is_satisfied_by(self, candidate):
    raise NotImplementedError

    def __and__(self, other):
    return AndSpecification(self, other)

    class AndSpecification(Specification):
    def __init__(self, spec1, spec2):
    self.spec1 = spec1
    self.spec2 = spec2

    def is_satisfied_by(self, candidate):
    return self.spec1.is_satisfied_by(candidate) and self.spec2.is_satisfied_by(candidate)

    class EnabledUserSpecification(Specification):
    def is_satisfied_by(self, user):
    return user.enabled

    class RichUserSpecification(Specification):
    def is_satisfied_by(self, user):
    return user.balance > 1000

    # 使用
    spec = EnabledUserSpecification() & RichUserSpecification()
    if spec.is_satisfied_by(user):
    # 业务逻辑


    十三、规约在验证、权限、限流等的应用延展

    • 验证:将校验逻辑(如邮箱唯一、手机号校验等)封装为规约,可以复用于多业务流程。
    • 权限/授权:可将访问权限判断封装为规约,易于组合各种授权策略。
    • 限流/风控:对于风控规则快速变化场景,使用规约可弹性组合和测试。

    十四、实践建议

  • 不要滥用规约:简单业务不要引入规约模式,否则反而增加复杂度。
  • 业务语言主导规约命名:用领域词汇表达规约名,如OrderCanBeCanceledSpecification。
  • 配合单元测试保证规约正确性:规约的复用性高,单元测试非常重要。
  • 多用组合方式表达变化:应优先通过组合表达业务变化,降低重写规约的成本。
  • 总结

    规约 是DDD重要的战术模式之一,能抽象、表达并组合领域对象的满足条件,为业务规则实现提供良好支持。实际应用中,要根据项目复杂度酌情采用,利用好规约的复用与组合价值。

    赞(0)
    未经允许不得转载:171主机测评 » 领域驱动设计DDD 规约详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址