领域驱动设计:如何模拟嵌套产品类别层次结构?更糟糕的是,如果产品类别是一个聚合根?
我正在实践领域驱动设计,那么为什么不建立一个演示产品目录项目?显然Product
在这里是核心领域,但因为我喜欢让项目更有趣,我很乐意支持嵌套Category
层次结构。换句话说,Category
可能有许多孩子Category
。领域驱动设计:如何模拟嵌套产品类别层次结构?更糟糕的是,如果产品类别是一个聚合根?
此外,我想分开Category
域从Product
域,并使其自己的支持域。
问题:标记Category
as AggregateRoot听起来不正确。 A Category
可能有许多孩子Category
,这也是AggregateRoots?!!我怎样才能对这个建模进行建模?嵌套产品类别在电子商务现实生活中非常普遍。
命名空间DL.Demo.Domain.Shared
public abstract class ValueObjectBase<T> : IEquatable<T>
where T : ValueObjectBase<T>
{
public abstract bool Equals(T other);
public abstract override bool Equals(object obj);
public abstract override int GetHashCode();
}
public abstract class EntityBase : IEquatable<EntityBase>
{
public Guid Id { get; private set; }
protected EntityBase()
{
this.Id = Guid.NewGuid();
}
// Some Object overrides
}
,我实际上是从Entity
AggregateRoot
inherents因为我想只有一个Entity
可能是一个AggregateRoot
?
public abstract class AggregateRoot : EntityBase
{
}
命名空间DL.Demo.Domain.Catalog
public class Category : AggregateRoot
{
public string Name { get; private set; }
public Guid? ParentCategoryId { get; private set; }
public CategoryStatus CategoryStatus { get; private set; }
}
有AggregateRoot的嵌套列表只是不健全的权利我。如果您不将Category
标记为AggregateRoot,那么您将如何对此进行建模?
我是DDD和所有其他相关的酷的东西,如域名事件,事件采购等新的我将不胜感激,如果有经验的人可以告诉我,如果我去正确的方式。
我是DDD和所有其他相关的酷东西,如域名事件,事件采购等新的我将不胜感激,如果有经验的人可以告诉我,如果我去正确的方式。
你是对的。
Category
应该是Aggregate root
,通过它的ID引用父类别,这非常好。
即使没有特殊的不变量来保护,因为这个层次结构可以在Read models
中投影出不同的模式,所以嵌套类别是一个很好的事件采购候选项。尽管Aggregate
非常简单,但您对该表示没有任何限制。在使用Read model
你可以有不同的实现他们为每一个:
- 与父模型树结构参考
- 模型树结构与子引用
- 模型树的结构与祖先
- 模型树的数组与物化路径结构 使用嵌套
- 模型树的结构设置
See more here about implementing tree structures(该链接指向MongoDB,但不相关)。
的Category Aggregate
刚刚发出简单的事件ACategoryWasCreated
,ACategoryHasMovedToOtherParent
等和Read models
刚刚适应,以反映这些事件。
我已经实现了像这样的树形结构,并且在读取端(查询端)的查询是非常快的。您可以选择一个类别中的产品和不加入任何类别的所有类别。或者,您可以再次建立一个类别路径,而无需连接。
我不担心类别是聚合根。我唯一觉得直的是一个类可以有子汇总根?!我没有在我的帖子中提及域名事件,但据推测聚合根域也会包含域名事件列表。所以父类别会有自己发出的事件列表,但它的孩子也有他们自己的事件列表?!这听起来不对。 –
我是否应该将CategoryTree设置为Aggregate root,而不是将类别设置为Aggregate root,并且它只是有一个Categories类别列表来启动递归类别层次结构?这样,CategoryTree可以包含整个Aggregate的所有域事件。 –
@DavidLiang你只保留'Aggregate'上的parentId,而不是整个父级或树。你不需要。该树建立在“读取模型”上。每个实例仅为其自身发出事件。 –
定义聚合的关键是首先定义一个事务边界。除了聚合边界以外,一致性最终是通过对聚合发出的域事件做出反应来实现的。
聚合可以持有另一个聚合ID(值对象)作为参考,但是,不负责与另一个聚合在事务上一致。
所以,主要问题 - 您的树在事务上是否一致?如果是的话,链表就不会很好地扩展。你必须以不同的方式进行建模。
建模是上下文特定的,并不是饼干练习。也许你的类别只是一个可以作为路径建模的价值对象。很难说没有更广泛的背景。
如果你想有一个类别树,那么树本身可能应该是你的聚合根(我已经看到你已经在你自己的评论中得出了这个结论)。这将有添加或删除孩子等功能。
是的,对于非常大的树木,您可能会获得很多性能,例如以json格式(存储在MongoDb,缓存,文件或其他)中的树的只读投影。特别是考虑如何更新这样的类树通常只读取其中的一小部分,您也可以轻松地离开,只需保持json和前面的规范化数据库表树。
那么,一个类别有什么行为?你需要在这些行为中执行哪些不变量?有很多方法可以模拟这个问题,这完全取决于行为和真实的不变量。 ProductCategory听起来不像一个非常有趣的聚合。我怀疑大多数行为将会是CRUD,因此域模型模式可能不是正确的选择。 – plalx
+1,我不会使用DDD战术模式(聚合等)为'类别',除非他们干涉'产品'域规则中复杂的方式。 – guillaume31
我正在做一些事情,因为这只是一个演示项目:当网站的管理员决定停用某个类别时,需要停用直接链接到该类别的所有产品以及该类别下的所有子类别,以及所有产品链接到这些类别?也可能需要发送电子邮件给所有其他管理员。此外,我们可能需要支持在其他类别下移动类别。这是一个复杂行为的好例子吗? –