Domain driven design 聚合根中的子实体的用途是什么?

Domain driven design 聚合根中的子实体的用途是什么?,domain-driven-design,Domain Driven Design,[此问题和评论的后续行动:] 正如标题所说:我不清楚作为一个孩子,实体的实际/确切目的是什么 根据我在许多地方读到的内容,这些是聚合子实体的属性: 它具有要聚合的本地标识 它不能直接访问,只能通过聚合根进行访问 它应该有方法 它不应暴露在骨料中 在我看来,这意味着几个问题: 实体应该是聚合的私有实体 我们需要一个只读复制值对象来公开实体中的信息(例如,至少对于存储库来说,为了保存到数据库,它能够读取信息) 我们在实体上拥有的方法在聚合上是重复的(反之亦然,我们必须在聚合上拥有的处理实体的方法在实

[此问题和评论的后续行动:]

正如标题所说:我不清楚作为一个孩子,实体的实际/确切目的是什么

根据我在许多地方读到的内容,这些是聚合子实体的属性:

  • 它具有要聚合的本地标识
  • 它不能直接访问,只能通过聚合根进行访问
  • 它应该有方法
  • 它不应暴露在骨料中
  • 在我看来,这意味着几个问题:

  • 实体应该是聚合的私有实体
  • 我们需要一个只读复制值对象来公开实体中的信息(例如,至少对于存储库来说,为了保存到数据库,它能够读取信息)
  • 我们在实体上拥有的方法在聚合上是重复的(反之亦然,我们必须在聚合上拥有的处理实体的方法在实体上是重复的)
  • 那么,为什么我们有一个实体,而不仅仅是值对象呢?只有值对象、聚合和公开值对象上的所有方法(我们已经在复制实体信息)要方便得多

    附言。 我想把重点放在聚合的子实体上,而不是实体集合上


    [更新对Constantin Galbenu的回应回答和评论]

    那么,实际上,你会有这样的东西

    public class Aggregate {
        ...
        private _someNestedEntity;
    
        public SomeNestedEntityImmutableState EntityState {
           get {
              return this._someNestedEntity.getState();
           }
        }
    
        public ChangeSomethingOnNestedEntity(params) {
           this._someNestedEntity.someCommandMethod(params);
        }
    }
    

    在CQRS体系结构中不存在这些问题,因为写入模型(聚合)不同于读取模型。在平面体系结构中,聚合必须公开读取/查询方法,否则它将毫无意义

  • 实体应该是聚合的私有实体
  • 是的,通过这种方式,你清楚地表达了这样一个事实:它们不是外用的

  • 我们需要一个只读复制值对象来公开实体中的信息(例如,至少对于存储库来说,为了保存到数据库,它能够读取信息)
  • 存储库是一种特殊情况,不应以与应用程序/表示代码相同的方式查看。它们可以是同一个包/模块的一部分,换句话说,它们应该能够访问嵌套的实体

    可以将实体视为/实现为具有不可变ID的对象和表示其状态的值对象,如下所示(在伪代码中):

    你明白了吗?您可以安全地返回嵌套实体的
    状态
    ,因为它是不可变的。可能会有一些问题,但这是一个你们必须做出的决定;如果您通过返回状态来破坏它,那么第一次编写代码会变得更简单,但耦合会增加

  • 我们在实体上拥有的方法在聚合上是重复的(反之亦然,我们必须在聚合上拥有的处理实体的方法在实体上是重复的)
  • 是的,这可以保护聚合的封装,还允许聚合保护其不变量

  • 实体应该是聚合的私有实体
  • 对。我不认为这是一个问题。继续阅读以了解原因

  • 我们需要一个只读的复制值对象来公开实体中的信息(至少对于存储库来说,它能够读取信息以便 保存到数据库(例如)
  • 否。使聚合返回需要在聚合的每个方法上的事件中持久化和/或引发的数据

    原始示例。现实世界需要更细粒度的响应,可能
    performMove
    函数需要使用
    game的输出。performMove
    持久性
    和eventPublisher构建支撑结构:

      public void performMove(String gameId, String playerId, Move move) {
        Game game = this.gameRepository.load(gameId); //Game is the AR
        List<event> events = game.performMove(playerId, move); //Do something
        persistence.apply(events) //events contains ID's of entities so the persistence is able to apply the event and save changes usign the ID's and changed data wich comes in the event too.
        this.eventPublisher.publish(events); //notify that something happens to the rest of the system
      }
    
    public void performMove(字符串玩家ID、字符串玩家ID、移动){
    Game Game=this.gameRepository.load(gameId);//游戏是AR
    List events=game.performMove(playerId,move);//做点什么
    persistence.apply(events)//事件包含实体的ID,因此持久性能够应用事件,并使用ID和事件中的更改数据保存更改。
    this.eventPublisher.publish(events);//通知系统的其余部分发生了一些事情
    }
    
    对内部实体执行相同的操作。让实体返回因其方法调用(包括其ID)而更改的数据,在AR中捕获此数据,并为persistence和eventPublisher生成propper输出。这样,您甚至不需要向AR公开实体ID为的公共只读属性,也不需要向应用程序服务公开AR的内部数据。这是摆脱Getter/setter包对象的方法

  • 实体上的方法与聚合上的方法相同(反之亦然,聚合上必须具有的处理实体的方法) 在实体上重复)
  • 有时,要检查和应用的业务规则只属于一个实体,其内部状态和AR只是充当网关。这是可以的,但如果你发现这种模式太多,那么这是一个错误的AR设计的迹象。也许内部实体应该是AR而不是内部实体,也许您需要将AR拆分为多个AR(其中一个是旧的ner实体),等等。。。不要因为类只有一个或两个方法而惊慌失措

    针对dee zg的意见:

    持续性应用(事件)确切地做什么?它能拯救整个世界吗 聚合还是仅实体

    都不是。聚合和实体是领域概念,而不是持久性概念;您可以拥有文档存储、列存储、关系存储等,而不需要与域概念1:1匹配。您不从Persience读取聚合和实体;使用从持久性读取的数据在内存中构建聚合和实体。聚合本身不需要持久化,这只是一个可能的实现细节。请记住,聚合只是一个组织业务规则的构造,而不是一个表示状态的结构

      public void performMove(String gameId, String playerId, Move move) {
        Game game = this.gameRepository.load(gameId); //Game is the AR
        List<event> events = game.performMove(playerId, move); //Do something
        persistence.apply(events) //events contains ID's of entities so the persistence is able to apply the event and save changes usign the ID's and changed data wich comes in the event too.
        this.eventPublisher.publish(events); //notify that something happens to the rest of the system
      }