假设我有一个名为Folder的AggregateRoot,其中包含以下子文件夹的集合:
class Folder : AggregateRoot
{
string Name { get; }
ICollection<Folder> Folders { get; }
}
这里的内部集合实际上只是其他Folder-aggregates的聚合ID列表,在枚举时会延迟解决。
这种结构在域建模中是否有效?在域建模中,聚合不仅引用其他聚合,还将其Folders-Property定义为其他Folder-aggregates的集合?
为什么?上面的示例可能不是特别好,但是我的目标主要是要有一种自然的方式来处理集合集合,并隐藏agg引用是通过地下存储库解析的事实。我想像使用实体集合一样轻松地处理聚合。
我在这里的想法还在于,子文件夹在某种程度上归父集合所有,集合实际上是一种表示集合存储位置的方式,即使存储集合不是真的也是如此通常是通过存储库。
具有递归性的示例并不十分重要。重点在于以下事实:集合“似乎”拥有其他集合。而且,当在两个文件夹中进行更改时,当然只能一一保存,但这没关系。我还必须包含一些规则,即只能在适当位置创建文件夹,而不能手动添加文件夹,以便它们可以出现在多个agg集合中。
答案 0 :(得分:0)
子结构在域建模中是有效的用例,在递归概念(如组,标签等)中经常遇到这种结构,就像在“文件夹”示例中一样。而且我喜欢将它们作为域层中的纯集合对象来处理,而没有任何持久逻辑的暗示。编写此类域逻辑时,我想像一下我正在处理对象,就像它们将被永久保存在RAM上一样。
我将以您的递归示例为例进行解释,但是同一概念适用于子对象的任何“集合”,而不仅仅是递归关系。
下面是伪代码的示例实现,并带有注释。我先向您道歉,该代码在结构上更接近于Python。我想准确地传达这个想法,而不用担心如何用C#来表达它,因为我对C#并不熟悉。如果不清楚,请询问有关代码的问题。
有关伪代码的注释:
FolderService
是通常由API调用的ApplicationService
。该服务负责组装基础结构服务,与域层交互以及最终的持久性。FolderTable
是Folder
对象的假想数据库表示。 FolderRepository
一路了解此类及其实现细节FolderRepository
类中存在从数据库保存和检索Folder对象的复杂性。load_by_name
存储库方法会急切加载所有子文件夹并将其填充到父文件夹中。我们可以将其转换为仅在访问时才是延迟加载的,除非我们进行遍历,否则就永远不要加载它(甚至可以根据要求进行分页,尤其是在子文件夹的数量没有特定限制的情况下)class Folder(AggregateRoot):
name: str
folders: List[Folder]
@classmethod
def construct_from_args(cls, params):
# This is a factory method
return Folder(**params)
def add_sub_folder(self, new_folder: Folder) -> None:
self.folders.append(new_folder)
def remove_sub_folder(self, existing_folder: Folder) -> None:
# Dummy implementation. Actual implementation will be more involved
for folder in self.folders:
if folder.name == existing_folder.name:
self.folders.remove(existing_folder)
class FolderService(ApplicationService):
def add_sub_folder(parent_folder_name: str, new_folder_params: dict) -> None:
folder_repo = _system.get_folder_repository()
parent_folder = folder_repo.load_by_name(parent_folder_name)
new_sub_folder = Folder.construct_from_args(new_folder_params)
parent_folder.add_sub_folder(new_sub_folder)
folder_repo.save(parent_folder)
class FolderTable(DBObject):
# A DB representation of the folder domain object
# `parent_id` will be empty for the root folder
name: str
parent_id: integer
class FolderRepository(Repository):
# Constructor for Repository
# that has `connection`s to the database, for later use
# FolderRepository works exclusively with `FolderTable` class
def load_by_name(self, folder_name: str) -> Folder:
# Load a folder, including its subfolders, from database
persisted_folder = self.connection.find(name=folder_name)
parent_identifier = persisted_folder.id
sub_folders = self.connection.find(parent_identifier)
for sub_folder in sub_folders:
persisted_folder.folders.append(sub_folder)
return persisted_folder
def save(self, folder: Folder) -> None:
persisted_folder = self.connection.find(name=folder.name)
parent_identifier = persisted_folder.id
# Gather the persisted list of folders from database
persisted_sub_folders = self.connection.find(parent_identifier)
for sub_folder in folder.folders:
# The heart of the persistence logic, with three conditions:
# If the subfolder is already persisted,
# Do Nothing
# If there is a persisted subfolder that is no longer a part of folders,
# Remove it
# If the subfolder is not among those already persisted,
# Add it
如果您发现此实现或我的思想过程有漏洞,请指出。
答案 1 :(得分:0)
当一个聚合包含另一个聚合时,应避免直接引用,因为通常会产生一些不必要的痛苦。
任何 lazy-loading 都会表明您可以重新设计一些内容,因为您也应该避免这种情况。
最常见的模式是仅具有ID列表或值对象列表。后者似乎更适合您的情况。这样,您就可以始终拥有一个包含所有相关文件夹的完全加载的AR。为了导航,您需要检索相关文件夹。
这个特定的示例具有某些特殊性,因为它表示层次结构,但是您必须根据具体情况来处理。
简而言之:不建议任何聚合引用另一个,无论是通过集合还是其他方式。