Domain driven design DDD:骨料设计-骨料之间的参考

Domain driven design DDD:骨料设计-骨料之间的参考,domain-driven-design,repository-pattern,unit-of-work,aggregateroot,onion-architecture,Domain Driven Design,Repository Pattern,Unit Of Work,Aggregateroot,Onion Architecture,我有一个关于如何设计聚合的问题 我有公司、市、省和国家实体。其中每一个都需要是其自身聚合的聚合根。城市、省和国家实体在整个系统中使用,并被许多其他实体引用,因此它们不是价值对象,也需要在许多不同的场景中访问。所以他们应该有存储库。CityRepository将具有FindByIdint、GetAll、GetByProvinceProvinceProvince、GetByCountryCountry、GetByNamestring等方法 以下面的例子为例。公司实体与属于某个国家的某个省的某个城市有

我有一个关于如何设计聚合的问题

我有公司、市、省和国家实体。其中每一个都需要是其自身聚合的聚合根。城市、省和国家实体在整个系统中使用,并被许多其他实体引用,因此它们不是价值对象,也需要在许多不同的场景中访问。所以他们应该有存储库。CityRepository将具有FindByIdint、GetAll、GetByProvinceProvinceProvince、GetByCountryCountry、GetByNamestring等方法

以下面的例子为例。公司实体与属于某个国家的某个省的某个城市有关联:

现在让我们假设我们有一个公司列表页面,其中列出了一些公司及其所在城市、省份和国家

按ID引用 如果一个实体需要引用一个城市、省或国家,他们将按照沃恩·弗农的建议,通过ID进行引用

为了从存储库中获取这些数据,我们需要调用4个不同的存储库,然后匹配数据以填充视图

var companies = CompanyRepository.GetBySomeCriteria();
var cities = CityRepository.GetByIds(companies.Select(x => x.CityId);
var provinces = ProvinceRepository.GetByIds(cities.Select(x => x.ProvinceId);
var countries = CountryRepository.GetByIds(province.Select(x => x.CountryId);

foreach(var company in companies)
{
    var city = cities.Single(x => x.CityId == company.CityId);
    var province = provinces.Single(x => x.ProvinceId == city.ProvinceId);
    var country = countries.Single(x => x.CountryId == province.CountryId);

    someViewModel = new CompanyLineViewModel(company.Name, city.Name, province.Name, country.Name);
}
这是一种非常笨重和低效的方法,但显然是“正确”的方法

参照 如果实体是通过引用引用的,则相同的查询将如下所示:

var companies = CompanyRepository.GetBySomeCriteria();
someViewModel = new CompanyLineViewModel(company.Name, company.City.Name, company.Province.Name, company.Country.Name);
但据我所知,这些实体不能被引用,因为它们存在于不同的集合中

问题 我如何才能更好地设计这些集合


我是否可以使用城市模型加载公司实体,即使它们存在于不同的集合中?我想这将很快打破集合之间的界限。更新聚合时处理事务一致性也会造成混乱。

您可以创建一个完全不同的对象,它只是一个表示视图模型的平面数据结构,可以直接从数据库中检索。Google Thin Read Layer或CQRS。

您可以创建一个完全不同的对象,它只是一个表示视图模型的平面数据结构,可以直接从数据库中检索。Google瘦阅读层或CQRS。

Dennis Traub已经指出了可以做些什么来提高查询性能。这种方法对于查询来说更加有效,但也更加庞大,因为您现在需要额外的代码来保持视图模型与聚合同步

如果您不喜欢这种方法,或者由于其他原因无法使用它,我不认为您建议的第一种方法比使用直接对象引用更无效或更庞大。假设您在聚合中使用了直接对象引用。如何将这些聚合持久化到持久存储?使用数据库时,您会想到以下选项:

如果您正在为公司使用非规范化的表,例如,使用文档数据库(如MongoDB),那么您已经在有效地优化视图查询了。然而,你需要所有额外的工作来保持你的公司表与省、市同步。效率高,但体积大,您可能会考虑保留实际视图模型而不是每个用例。 如果将规范化表与关系数据库一起使用,则可以使用Company表中的外键通过其id引用相应的城市、省份等。查询公司时,为了检索填充视图模型所需的城市、省份等字段,可以使用4+个表的联接,或者使用4个独立的查询来查询城市、省。。。表,例如,当外键引用使用延迟加载时。 如果您在非关系数据库中使用规范化表,通常人们使用的代码与您建议的完全相同。对于某些数据库,ORM工具(如Morphia或Datanucleus)可以为您节省一些编程工作,但在幕后,独立查询仍然存在。 因此,在第二个和第三个选项中,如果让一个ORM解决方案为您生成数据库映射,您可以节省一些琐碎的编程工作,但效率没有得到很大提高。连接可以通过适当的索引进行优化,但要正确完成这一点并非易事

但是,我想指出的是,当您按照Id引用并使用编程应用程序端连接时,您仍然可以完全控制视图模型对象构造和数据库查询,正如您所建议的代码所示。 特别是,城市、省份等的名称通常变化很小,而且变化很少,很容易记忆。因此,您可以在数据库查询中广泛使用内存缓存,甚至可以使用在应用程序启动时从平面文件填充的内存存储库。正确完成后,要为公司构建视图模型,只需要对公司表进行一次数据库调用,其他字段将从中检索-
内存缓存/存储库,我认为这是非常有效的。

< P> Dennis Traub已经指出了你可以做什么来提高查询性能。这种方法对于查询来说更加有效,但也更加庞大,因为您现在需要额外的代码来保持视图模型与聚合同步

如果您不喜欢这种方法,或者由于其他原因无法使用它,我不认为您建议的第一种方法比使用直接对象引用更无效或更庞大。假设您在聚合中使用了直接对象引用。如何将这些聚合持久化到持久存储?使用数据库时,您会想到以下选项:

如果您正在为公司使用非规范化的表,例如,使用文档数据库(如MongoDB),那么您已经在有效地优化视图查询了。然而,你需要所有额外的工作来保持你的公司表与省、市同步。效率高,但体积大,您可能会考虑保留实际视图模型而不是每个用例。 如果将规范化表与关系数据库一起使用,则可以使用Company表中的外键通过其id引用相应的城市、省份等。查询公司时,为了检索填充视图模型所需的城市、省份等字段,可以使用4+个表的联接,或者使用4个独立的查询来查询城市、省。。。表,例如,当外键引用使用延迟加载时。 如果您在非关系数据库中使用规范化表,通常人们使用的代码与您建议的完全相同。对于某些数据库,ORM工具(如Morphia或Datanucleus)可以为您节省一些编程工作,但在幕后,独立查询仍然存在。 因此,在第二个和第三个选项中,如果让一个ORM解决方案为您生成数据库映射,您可以节省一些琐碎的编程工作,但效率没有得到很大提高。连接可以通过适当的索引进行优化,但要正确完成这一点并非易事

但是,我想指出的是,当您按照Id引用并使用编程应用程序端连接时,您仍然可以完全控制视图模型对象构造和数据库查询,正如您所建议的代码所示。
特别是,城市、省份等的名称通常变化很小,而且变化很少,很容易记忆。因此,您可以在数据库查询中广泛使用内存缓存,甚至可以使用在应用程序启动时从平面文件填充的内存存储库。当做对了,为公司构建视图模型时,只需要一个数据库调用公司表,而其他字段从内存缓存/存储库中检索,我认为这是非常有效的。

存储库是用于聚合存储的,而不是用于复杂读取的。存储库用于聚合存储,不用于复杂读取。