在聚合根的子实体 - DDD上实施具有范围的不变量

时间:2017-11-28 13:44:21

标签: oop domain-driven-design aggregateroot invariants

我试图了解如何表示某些DDD(域驱动设计)规则。 按照蓝皮书大会,我们有:

  • 根实体具有全局标识,负责检查不变量。
  • 根实体控制访问权限,不能因内部更改而瞎了。
  • 可以传递对内部成员的瞬时引用,仅用于单个操作。

当客户端可以访问内部实体时,我很难找到强制执行不变量的最佳方法。

这个问题当然只会在子实体可变的情况下发生。

将此玩具示例设置为Car,其中包含四个Tire(s)。我想要依次跟踪每个Tire的使用情况。

显然Car聚合根Tire儿童实体

  

商家规则:无法将Milage添加到单个Tire。当连接到Car

时,Milage只能添加到所有4个轮胎中

天真实施将是:

public class Tire
{
    public double Milage { get; private set;  }
    public DateTime PurchaseDate { get; set; }
    public string ID { get; set; }
    public void AddMilage(double milage) => Milage += milage;
}

public class Car
{
    public Tire FrontLefTire { get; private set; }
    public Tire FrontRightTire { get; private set; }
    public Tire RearLeftTire { get; private set; }
    public Tire RearRightTire { get; private set; }

    public void AddMilage (double milage)
    {
        FrontLefTire.AddMilage(milage);
        FrontRightTire.AddMilage(milage);
        RearLeftTire.AddMilage(milage);
        RearRightTire.AddMilage(milage);
    }

    public void RotateTires()
    {
        var oldFrontLefTire = FrontLefTire;
        var oldFrontRightTire = FrontRightTire;
        var oldRearLeftTire = RearLeftTire;
        var oldRearRightTire = RearRightTire;

        RearRightTire = oldFrontLefTire;
        FrontRightTire = oldRearRightTire;
        RearLeftTire = oldFrontRightTire;
        FrontLefTire = oldRearLeftTire;
    }

    //...
}

但是Tire.AddMilage方法是公开的,这意味着任何服务都可以执行以下操作:

Car car = new Car(); //...

// Adds Milage to all tires, respecting invariants - OK
car.AddMilage(200); 

//corrupt access to front tire, change milage of single tire on car
//violating business rules - ERROR
car.FrontLefTire.AddMilage(200); 

我想到了可能的解决方案:

  1. events上创建Tire以验证更改,并在Car上实施
  2. Car成为Tire的工厂,在其构造函数上传递 TireState ,并持有对它的引用。
  3. 但我觉得应该有一种更简单的方法来做到这一点。

    您怎么看?

2 个答案:

答案 0 :(得分:1)

  

可以传递对内部成员的瞬时引用,仅用于单个操作。

自蓝皮书撰写以来,这种做法已经发生了变化;传递对支持变异操作的内部成员的引用尚未完成。

考虑这一点的方法是采用聚合API(当前支持查询和命令),并将该API拆分为两个(或更多)接口;一个支持命令操作,另一个支持查询。

命令操作仍然遵循通常的模式,提供应用程序可以让聚合自行更改的路径。

查询操作返回包含 no 变异操作的接口,既不直接也不代理。

root.getA() // returns an A API with no mutation operations
root.getA().getB() // returns a B API with no mutation operations

查询一直是查询。

在大多数情况下,您可以避免完全查询实体;而是返回表示实体当前状态的

避免共享子实体的另一个原因是,在大多数情况下,选择将聚合的该部分建模为单独的实体是您可能希望在域模型中更改的决策。通过在API中公开实体,您将在该实现选择与API的使用者之间创建耦合。

(考虑到这一点的一种方式:汽车集合不是'汽车"它是'文件"描述"汽车" 34; API应该将应用程序与文档的特定细节隔离开来。)

答案 1 :(得分:0)

轮胎应该没有吸气剂。

吸气剂让你陷入困境。删除getter不仅仅是DDD Aggregte Roots的问题,而是OO,Demeter法则等问题。

想一想为什么你需要一辆汽车的轮胎并将这个功能转移到汽车本身。