【问题标题】:Behavior of aggregate roots in DDD should be as actor or not?DDD 中聚合根的行为是否应该作为参与者?
【发布时间】:2018-02-06 05:39:10
【问题描述】:

我的英语不好,所以我举两个例子。在这个例子中,让我们从 DDD 中删除一些甚至重要的东西,主要的是事情的本质。

从 DDD 的角度来看如何做?

我们有两个聚合根,SellerAdvertSeller 可以在这些示例中编辑 Advert

1.

如果模型应该反映真实的业务逻辑。然后是Seller 必须更改Adverts。即客户端层调用方法changeAdvertName()changeAdvertCost() 的聚合Seller。顺便说一句,这提供了访问检查等优势。我们可以看到Seller 只能修改自己的Adverts。这是尽可能的第一个选项。

    //Client layer call seller.changeAdvertName(name)

    //AR Seller
    class Seller{
        adverts
        changeAdvertName(advertId, name){
            adverts[advertId].changeName(name)
        }
        changeAdvertCost(advertId, cost){
            adverts[advertId].changeCost(cost)
        }
    }

    //AR Advert
    class Advert{
        name
        cost
        changeName(name){
            this.name = name
        }
        changeCost(cost){
            this.cost = cost
        }
    }

2.

另一种变体,客户端层可以直接从聚合Advert 调用方法changeNamechangeCost。我多次看到这个实现。

    //Client layer call advert.changeName(name)

    //AR Advert
    class Advert{
        name
        cost
        changeName(name){
            this.name = name
        }
        changeCost(cost){
            this.cost = cost
        }
    }

您如何看待这些选项?它们都对 DDD 有效吗
执行?从 DDD 的角度来看,哪一个更正确和合乎逻辑?

谢谢!

【问题讨论】:

    标签: architecture entity domain-driven-design aggregateroot


    【解决方案1】:

    它们都对 DDD 实施有效吗?

    领域驱动设计中的一个重要思想是一致性边界的概念 - 聚合是围绕状态的边界,可以单独修改 - 无需查看聚合之外的任何状态。

    主要好处是客户端代码无需担心管理一致性规则;这种责任是集体的。

    另一个好处是对一个聚合的修改不需要阻止对另一个聚合的修改。

    嵌套聚合根,通过让一个聚合持有对另一个聚合的引用,会使这个想法变得一团糟;尝试修改广告的线程可能会干扰尝试修改广告卖家的其他线程。

    在一个聚合中包含多个实体基本上没有错。例如,您可以合理地将卖家实体和广告实体合并为一个卖家集合,并通过卖家对广告进行所有更改来强制执行您的一致性保证。然而,重要的是要认识到,在这种设计中,广告本身并不是聚合根。

    让 Advert 成为自己的聚合根,处理自己的一致性规则,而卖方生活在不同的聚合中也没有错。

    在这个简单的示例中,卖家只是推迟对广告的更改,因此将它们彼此分开是有意义的,以便可以同时修改同一卖家的不同广告。

    如果有一些跨越多个广告的关键域不变量,那么您可能需要将它们全部拉入一个集合中,该集合可能存在于卖方聚合中。

    我的想法是,在真正的业务流程中,卖家专门创建广告并更改广告。但不是抽象的客户端层创建和更改 Advert。那么,您能帮忙理解一下吗?

    事实证明,在真实的商业世界中,卖方可以创建()、删除()、修改()... -> 广告。但是在 DDD 聚合中,Seller 只能实现 create(Advert) 行为?

    这确实不是特定于 DDD;它更多地反映了“面向对象”编程(如 Java 等所理解的那样)。行为——也就是说,状态的改变——通过向管理该状态的实体发送消息来发生。

    面向对象的习语实际上与英语语法并不匹配。我们通常写“卖方修改广告”——一种主谓宾形式。但是在面向对象编程的语法中,对象会改变自己的状态以响应命令式时态消息(命令)。

    List.addItem(...) - 我们没有修改列表,我们正在向列表发送一条命令:“修改您自己的状态”。

    同样,卖家不会修改广告的状态;她正在发送一条消息,描述广告应该改变的方式,这取决于广告。

    这是故意的:这意味着卖方可以与广告合作无需了解有关广告实施的任何信息,这意味着我们可以在不破坏卖方的情况下随时替换该实施。

    【讨论】:

    • 谢谢!我的想法是,在真正的业务流程中,特别是卖方创建广告并更改广告。但不是抽象的客户端层创建和更改 Advert。那么,您能帮忙理解一下吗?应该像抽象客户端层更改 Advert 一样调用行为(方法)?
    • 事实证明,在现实商业世界中Seller 可以create(),drop(),modify()... -> Advert。但是在DDD聚合中Seller只能实现create(Advert)行为?
    • @YuryGolikov 我会尽可能避免将行为放在主题实体上,除非是出于创造目的。例如。 advert = seller.advertise(...)advert.end(seller)...
    • @plalx 您能否详细说明避免对主题实体的行为?您的意思是聚合根操作的实体吗?
    • @Tenek 不,我只是指代表主题/角色(例如用户)的聚合。
    【解决方案2】:

    聚合根是一致性边界,以确保域模型保持在可靠状态。 我们从许多 DDD 从业者那里知道这一点。

    事务不应跨越聚合边界。要更新另一个聚合,请在单独的事务中使用域事件。 more

    从这个角度来看,第二个选项会更有效,因为您有 2 个聚合根。

    【讨论】:

      猜你喜欢
      • 2016-06-28
      • 2011-06-20
      • 2014-01-15
      • 1970-01-01
      • 2015-08-11
      • 2012-10-03
      • 1970-01-01
      • 2016-03-29
      • 1970-01-01
      相关资源
      最近更新 更多