【问题标题】:Aggregate or entity without business attributes没有业务属性的聚合或实体
【发布时间】:2019-04-28 13:43:31
【问题描述】:

关于下面的摘录,关于 cqrs 和 ddd,来自Patterns, Principles, and Practices of Domain-Driven Design by Nick Tune, Scott Millett

是不是说命令端的领域模型可以省略大部分业务属性? 例如客户实体会是什么样子?

客户实体可以省略名字、姓氏等吗? 如果是这样,这些业务属性会在哪里?仅在 CustomerEntity 中的读取模型中?

或者除了包含所有业务属性的 CustomerEntity 之外,还有 CustomerAggregate 以 1:1 关系包装 CustomerEntity,并且命令对象将在 CustomerAggregate 上运行? (对我来说似乎很奇怪)。

客户实体没有意义”是什么意思?

【问题讨论】:

    标签: domain-driven-design cqrs


    【解决方案1】:

    您指出的文字意味着您不必为整个系统甚至整个有界上下文建模可重用的实体(不要为可重用的现实生活中的事物建模)。这样做是一个糟糕的设计。

    您必须为执行操作的聚合建模。您仅向聚合提供且仅提供执行该操作所需的数据和聚合响应,即域遭受的更改,是您必须坚持的内容。

    为什么是实体和 V.O.?

    为了建模一致性,封装和解耦是基本部分,但这些是实现细节。对于 DDD,重要的是不同的角色(或概念)。

    当提供聚合(构造函数、函数调用参数等)时,聚合必须知道它是否与实体和/或 V.O. 一起工作。构建它的响应。

    如果域操作意味着实体属性的更改(在整个系统中具有唯一标识的东西),那么聚合的响应(一旦检查了所有规则和不变量)应该包括新的属性值和标识允许持久化更改的实体。

    因此,默认情况下,每个聚合都有自己的实体,具有唯一标识和聚合操作所需的属性。

    一个聚合可以有一个带有 ID 及其名称的客户实体。 另一个聚合可能有一个带有 ID 及其 Karma 点的 Customer 实体。

    因此,每个聚合都有自己的内部客户实体可供使用。当您提供聚合时,您传递客户数据(即 ID 和名称或 ID 和 Karma 点)并且聚合将该信息视为一个实体(如果聚合内部存在结构、类等,则这是一个实现细节问题表示实体)。

    一件重要的事情:如果您只需要处理实体 ID,则将其视为 V.O. (CustomerIdentityVO) 因为 ID 是不可变的,并且在该操作中,您可能只需要在持久性的某个字段中写入此 CustomerIdentityVO,而不更改任何 Customer 属性。

    这是标准愿景。一旦您开始识别与多个聚合相关的通用结构,或者一个可以使用相同数据执行多个操作的聚合,您就开始重构、重用等。这只是良好的 OOP 设计和 SOLID 原则的问题。

    请注意,我正在努力超越实施细节。我知道您几乎总是会有不想要的工件,这取决于编程范例类型、选择的编程语言等,但这种方法有助于避免您可能拥有的更糟糕的工件。

    推荐阅读:

    http://blog.sapiensworks.com/post/2016/07/29/DDD-Entities-Value-Objects-Explained http://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-1 http://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-2 https://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-3

    https://blog.sapiensworks.com/post/2016/08/19/DDD-Application-Services-Explained

    为了一个完整的拼图视觉。

    【讨论】:

    • 这是一个很好的答案。我知道应该这样做,但我不太确定。我一直在寻找关于这个主题的信息,这些信息会被很好地简洁地呈现出来。
    • 我要补充一句来自 Sapiens 的重要句子:“请记住,聚合只是一种组织业务规则的构造,它并不意味着代表状态。”这是一个非常有力的句子,它释放了我的思想。
    【解决方案2】:

    如果您使用的是Event Sourcing,那么您确实可以对聚合进行建模,而无需添加它们实现业务逻辑不需要的属性。

    这是一个例子:

    class Customer {
    
        public Guid ID { get; private set; }
    
        public Customer(Guid id, firstName, lastName, ...) {
    
            ID = id;
    
            this.AddEvent(new CustomerCreatedEvent(id, firstName, ....);
        }
    
        public void ChangeName(firstName, lastName) {
           this.AddEvent(new CustomerRenamedEvent(this.ID, firstName, lastName), 
        }
    }
    

    Custom 仅具有 ID 属性,因为它需要将其添加到它生成的每个事件中。 FirstNameLastName 被省略,因为即使在调用 ChangeName 方法时也不需要它们。它只记录发生这种情况的事件。如果您的逻辑需要 FirstName,那么您可以添加它。您可以省略任何不需要的属性。

    在这种情况下,您的 存储库 将只保存事件,而不关心客户属性的值。

    在读取端,您可能需要这些属性,因为您将向用户显示它们。

    如果您的聚合不是事件来源的,那么您可能需要更多的聚合属性来实现它的逻辑,并将它们保存到数据库中。

    这是一个例子:

    class Customer {
    
        public Guid ID { get; private set; }
        public string FirstName { get; private set; }
        public string LastName { get; private set; }
    
        public void ChangeName(firstName, lastName) {
           FirstName = firstName;
           LastName = lastName;
        }
    }
    

    在这种情况下,您的 Repository 将需要这些属性,因为它会生成查询以使用新值更新数据库。

    不确定“客户实体没有意义”是什么意思。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-14
      • 2012-04-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多