代码之家  ›  专栏  ›  技术社区  ›  tlt

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

  •  2
  • tlt  · 技术社区  · 6 年前

    [此问题的后续行动和评论: Should entity have methods and if so how to prevent them from being called outside aggregate ]

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

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

    1. 它具有要聚合的本地标识
    2. 它不能直接访问,只能通过聚合根进行访问
    3. 它应该有方法
    4. 不应暴露在骨料中

    在我看来,这意味着几个问题:

    1. 实体应该是聚合的私有实体
    2. 我们需要一个只读的copy-Value对象来公开实体中的信息(例如,至少对于存储库来说,为了保存到db,它能够读取)
    3. 我们在实体上拥有的方法在聚合上是重复的(反之亦然,我们必须在聚合上拥有的处理实体的方法在实体上是重复的)

    那么,为什么我们要有一个实体而不是只有值对象呢?只有值对象、聚合上的所有方法和公开值对象(我们已经在复制实体信息)要方便得多。

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


    [更新以响应 康斯坦丁·加尔贝努 回答和评论]

    所以,实际上,你会有这样的东西?

    public class Aggregate {
        ...
        private _someNestedEntity;
    
        public SomeNestedEntityImmutableState EntityState {
           get {
              return this._someNestedEntity.getState();
           }
        }
    
        public ChangeSomethingOnNestedEntity(params) {
           this._someNestedEntity.someCommandMethod(params);
        }
    }
    
    5 回复  |  直到 6 年前
        1
  •  2
  •   jlvaquero    6 年前
    1. 实体应该是聚合的私有实体

    对。我不认为这是个问题。继续阅读以了解原因。

    1. 我们需要一个只读的copy-Value对象来公开实体中的信息(至少对于一个存储库来说能够读取它以便 例如,保存到数据库)

    否。使聚合返回需要在聚合的每个方法上的事件中持久化和/或引发的数据。

    原始示例。现实世界需要更精细的反应,也许 performMove 函数需要使用 game.performMove 建造支撑结构 persistence 和事件发布者:

      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
      }
    

    对内部实体执行相同的操作。让实体重新运行由于其方法调用(包括其ID)而更改的数据,在AR中捕获此数据,并为持久性和eventPublisher构建propper输出。这样,您甚至不需要向AR公开实体ID为的公共只读属性,也不需要向应用程序服务公开AR的内部数据。这是摆脱Getter/Setters包对象的方法。

    1. 我们在实体上拥有的方法在聚合上是重复的(反之亦然,我们在处理实体的聚合上必须拥有的方法) 在实体上重复)

    有时,业务规则要检查和应用,只属于一个实体,其内部状态和AR只是充当网关。这是可以的,但如果你觉得这个模式太多,那么这是一个错误的AR设计的迹象。也许内部实体应该是AR而不是内部实体,也许您需要将AR拆分为几个AR(其中一个是旧的ner实体),等等。。。不要对只有一个或两个方法的类感到吃惊。

    针对dee zg的意见:

    是什么持久性.apply(事件)确切地说是什么?它能拯救整个世界吗 集合还是实体?

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

    您的事件具有上下文(用户意图)和已更改的数据(以及标识持久性中的内容所需的ID),因此编写一个 apply 持久层中的函数,它知道在关系数据库中执行什么sql指令,执行什么以应用事件并持久化更改。

    你能举例说明什么时候更好(甚至更好)吗 不可避免的?)使用子实体而不是单独的AR引用 它的Id作为值对象?

    你为什么要设计一个有状态和行为的班级?

    抽象、封装、重用等 SOLID 设计。如果实体拥有确保操作的域规则和不变量所需的一切,那么该实体就是该操作的AR。如果您需要实体无法完成的额外域规则检查(即,实体没有足够的内部状态来完成检查,或者不能自然地融入实体和所代表的内容),那么您必须重新设计;有时可以对执行额外域规则检查的聚合进行建模,并将其他域规则检查委托给内部实体,有时可以更改实体以包含新事物。这是太域上下文依赖,所以我不能说有一个固定的重新设计策略。

    请记住,不要在代码中对聚合和实体建模。您只需要用行为来对类建模,以检查域规则以及执行检查和响应更改所需的状态。这些类可以作为不同操作的聚合或实体。这些术语仅用于帮助沟通和理解类在每个操作上下文中的角色。当然,您可能会遇到这样的情况:操作不适合实体,您可以使用V.O.持久性ID对聚合进行建模,这样就可以了(遗憾的是,在DDD中,在不知道域上下文的情况下,默认情况下几乎一切都可以了)。

    你想从比我解释得更好的人那里得到更多启示吗?(不以英语为母语是这些复杂问题的障碍)看看这里:

    https://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-1 http://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-2 http://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-3

        2
  •  1
  •   guillaume31    6 年前
    1. 它具有要聚合的本地标识

    在逻辑意义上,可能是这样的,但具体地用持久性来实现这一点意味着我们所拥有的往往是不必要的复杂。

    1. 我们需要一个只读的拷贝值对象来公开 实体(至少存储库能够读取它以便 例如,保存到数据库)

    不一定,例如,您可以拥有只读实体。

    问题的存储库部分已在中解决 another question . 读取不是问题,有多种技术可以防止外部世界的写入访问,但仍然允许持久层直接或间接地填充实体。

    那么,为什么我们要有一个实体而不是只有值对象呢?

    你可能有点草率地把关注点放在同一个篮子里,而事实上这些关注点略有不同

    • 操作的封装
    • 聚合级别不变强制
    • 读访问
    • 写访问
    • 实体或VO数据完整性

    仅仅因为值对象最好是不可变的,并且不强制执行聚合级别不变量(尽管它们强制执行自己的数据完整性),并不意味着实体不能对某些相同的特性进行微调组合。

        3
  •  1
  •   Robert Bräutigam    6 年前

    你在考虑数据。住手。:)实体和值对象不是数据。它们是可以用来为问题域建模的对象。实体和值对象只是对问题建模时自然产生的事物的分类。

    实体应该是聚合的私有实体

    对。此外,对象中的所有状态都应该是私有的,并且从外部无法访问。

    我们需要一个只读的copy-Value对象来公开实体中的信息(例如,至少对于存储库来说,为了保存到db,它能够读取)

    不,我们不会公开已经存在的信息。如果信息已经存在,那就意味着已经有人对此负责了。因此,联系该对象为您做事情,您不需要数据!这基本上就是 Law of Demeter 告诉我们。

    经常实施的“存储库” 你需要访问数据,你说得对。他们是一个坏模式。它们通常与ORM结合在一起,在这种情况下更糟糕,因为您将失去对数据的所有控制。

    我们在实体上拥有的方法在聚合上是重复的(反之亦然,我们必须在聚合上拥有的处理实体的方法在实体上是重复的)

    诀窍是,你不必这么做。你创建的每个对象(类)都是有原因的。如前所述,为了创建额外的抽象,对域的一部分进行建模。如果这样做,一个存在于更高抽象级别上的“聚合”对象将永远不希望提供与下面的对象相同的方法。这意味着没有任何抽象。

    这个用例只在创建面向数据的对象时出现,这些对象除了保存数据之外几乎没有其他作用。显然,你会想,如果你不能把数据拿出来,你怎么能用这些做任何事情。然而,这是一个很好的指标,你的设计还没有完成。

        4
  •  0
  •   Constantin Galbenu    6 年前

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

    1. 实体应该是聚合的私有实体

    是的,这样你就清楚地表达了它们不是外用的事实。

    1. 我们需要一个只读的copy-Value对象来公开实体中的信息(例如,至少对于存储库来说,为了保存到db,它能够读取)

    存储库是一个特例,不应以与应用程序/表示代码相同的方式查看。它们可以是同一个包/模块的一部分,换句话说,它们应该能够访问嵌套的实体。

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

    class SomeNestedEntity
    {
        private readonly ID;
        private SomeNestedEntityImmutableState state;
    
        public getState(){ return state; }
        public someCommandMethod(){ state = state.mutateSomehow(); }
    }
    

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

    1. 我们在实体上拥有的方法在聚合上是重复的(反之亦然,我们必须在聚合上拥有的处理实体的方法在实体上是重复的)

    是的,这保护了聚合的封装,也允许聚合保护它的不变量。

        5
  •  0
  •   choquero70    6 年前

    我不会写太多。只是一个例子。一辆汽车和一个齿轮。汽车是根的集合体。齿轮是一个子实体