关于cqrs和ddd的以下摘录,来自Patterns, Principles, and Practices of Domain-Driven Design by Nick Tune, Scott Millett
这是否意味着命令端的域模型可以省略大多数业务属性?对于例如客户实体,它会是什么样子?
客户实体可以省略FirstName,Surname等吗?如果是这样,这些业务属性将在哪里?仅在CustomerEntity的阅读模型中?
或者除了包含所有业务属性的CustomerEntity之外,还有CustomerAggregate以1:1的关系包装CustomerEntity,并且命令对象将在CustomerAggregate上运行? (我觉得很奇怪)。
它是什么意思“客户实体没有意义”?
您指向的文本意味着您不必为整个系统建模可重用实体,甚至不需要为整个有界环境建模(不要为可重复使用的现实生活模型建模)。这样做是一个糟糕的设计。
您必须为执行操作的聚合建模。您仅使用执行该操作所需的数据,而且仅使用聚合响应(即域所遭受的更改)来提供聚合,这是您必须坚持的。
为什么实体和V.O.呢?
为了建模一致性,封装和解耦是基本部分,但这些是实现细节。对于DDD而言,重要的是不同的角色(或概念)。
在提供聚合(构造函数,函数调用参数等)时,聚合必须知道它是否与实体和/或V.O.一起使用。建立自己的回应。
如果域操作意味着实体属性的更改(在整个系统中具有唯一标识的内容),则聚合的响应(一旦检查了所有规则和不变量)应包括新属性值和该实体的标识允许持续更改。
因此,默认情况下,每个聚合都有自己的实体,其中包含唯一标识和聚合操作所需的属性。
一个聚合可以有一个具有ID及其名称的Customer实体。另一个聚合可以有一个具有ID及其Karma点的Customer实体。
因此,每个聚合都有自己的内部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
完整的拼图视觉。
如果您正在使用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),
}
}
自定义只有ID属性,因为它需要将它添加到它生成的每个事件。省略FirstName和LastName,因为即使调用ChangeName方法也不需要它们。它只记录发生这种情况的事件。如果您的逻辑需要FirstName,那么您可以添加它。您可以省略任何不需要的属性。
在这种情况下,您的存储库将仅保存事件,而不关心客户属性的值。
在Read端,您可能需要这些属性,因为您将向用户显示这些属性。
如果您的聚合不是事件源,那么您可能需要更多聚合属性来实现它的逻辑,它们将被保存到数据库中。
这是一个例子:
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;
}
}
在这种情况下,您的存储库将需要这些属性,因为它将生成一个查询以使用新值更新数据库。
不确定“客户实体没有意义”的含义。