更新聚合中的实体

17

我看到了一个类似的问题在SO上:如何更新聚合内的实体,但我仍然不确定用户界面应该如何与聚合内的实体交互。

假设我有一个User,拥有一些Address。User是聚合根,而Address只存在于聚合内部。

在网页界面上,用户可以编辑他的地址。基本上,发生以下情况:

  • 用户在他的网页界面上看到了地址列表
  • 他点击一个地址,被重定向到这个页面:edit-address?user=1&address=2
  • 在这个页面上,他得到一个表单,可以修改这个地址。

如果我们决定绕过聚合根,这将是很直接的:

  • 我们将直接加载具有其IdAddress
  • 我们将更新它,然后保存它

因为我们想按DDD方式做,我们有不同的解决方案:

  1. 要么我们请求User通过Id获取此Address

    address = user.getAddress(id);
    address.setPostCode("12345");
    address.setCity("New York");
    em.persist(user);

    这种方法的问题是,聚合根仍然对地址的处理没有更多的控制。它只返回一个对它的引用,所以与绕过聚合没有太大区别。

  2. 或者我们告诉聚合更新一个现有的地址

    user.updateAddress(id, "12345", "New York");
    em.persist(user);

    现在聚合根可以控制对该地址的操作,并且可以采取任何必要的行动来更新地址。

  3. 或者我们将地址视为一个值对象,不更新我们的Address,而是删除并重新创建它

    user.removeAddress(id);
    address = new Address();
    address.setPostCode("12345");
    address.setCity("New York");
    user.addAddress(address);
    em.persist(user);

    这个解决方案看起来很优雅,但意味着一个地址不能成为实体。那么,如果需要将其视为实体,例如因为聚合根内的另一个业务对象引用了它,怎么办?

我相信我缺少了理解聚合概念以及它在实际场景中的应用的一些内容,请不要犹豫给出你的评论!

1个回答

9
不,你没有错过任何东西——在大多数情况下,最好的选择是第二种方法(虽然我会将该方法称为changeAddress而不是updateAdress ——更新似乎不符合DDD)。通过普遍语言,你宁可说用户更改了他的地址,因此这正是你应该建模的方式——changeAddress方法可以决定是否更新属性(如果地址是一个实体)或者分配全新对象(当它是值对象时)。
以下样例代码假设最常见的情况——地址作为VO:
    public void ChangeAddress(AddressParams addressParams)
    {
        // here we might include some validation

        address = new Address(addressParams);

        // here we might include additional actions related with changing address
        // for example marking user as required to confirm address before
        // next billing
    }

在这个示例中,重要的是一旦创建了地址,它就被认为是有效的——在您的聚合中不会有无效的地址对象。但请记住,是否应该遵循此示例取决于您的实际领域——没有一个标准路径可供遵循。不过,这是最常见的路径。
是的,您应该始终通过遍历聚合根来执行实体操作——这个原因在SO的许多答案中都有说明(例如在这个Basic Aggregate Question中)。
某个东西是实体还是值对象取决于需求和您的领域。大多数情况下,地址只是一个值对象,因为具有相同值的两个地址之间没有区别,并且地址倾向于在其生命周期内不发生变化。但再次强调,这是大多数情况,并取决于您建模的领域。
另一个例子——对于大多数领域,Money将是一个值对象——10美元就是10美元,除了金额外没有任何身份。然而,如果您对处理账单的货币进行建模,则每张账单都有自己的身份(用某种唯一的数字表示),因此它将是一个实体。

谢谢。这确实有道理,尽管我不确定如何实现changeAddress()方法。上面的示例是过于简化了,一个地址可能由十几个字段组成,包括街道名称、建筑物编号、LatLng点表示地图上的精确位置等等。那么你建议怎么做呢?将所有这些参数放在方法参数中,还是从表单创建某种瞬态对象(也是Address对象?),并将其作为参数传递给此方法? - BenMorel
我已经添加了最常见情况的示例代码。不要担心字段的数量 - DDD 实际上并不关注数据,而是关注关系和管理复杂性。 - kstaruch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接