我遇到了如何设计聚合的问题。
我有Company
,City
,Province
和Country
个实体。这些中的每一个都需要是其自身聚合的聚合根。 City
,Province
和Country
实体在整个系统中使用,并被许多其他实体引用,因此它们不是值对象,也需要在许多不同的场景中访问。所以他们应该有存储库。 CityRepository
会有FindById(int)
,GetAll()
,GetByProvince(Province)
,GetByCountry(Country)
,GetByName(string)
等方法。
采用以下示例。 Company
实体与City
相关联,属于属于Province
的{{1}}:
现在让我们说我们有一个公司列表页面,列出了一些公司的城市,省份和国家。
如果某个实体需要引用Country
,City
或Province
,则他们会通过ID(按照Vaughn Vernon的建议)进行操作。
为了从存储库中获取这些数据,我们需要调用4个不同的存储库,然后匹配数据以填充视图。
Country
这是一个非常庞大和低效的,但显然是正确的'方式是什么?
如果引用被引用引用,则相同的查询将如下所示:
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);
}
但据我所知,这些实体不能通过引用引用,因为它们存在于不同的聚合中。
我还能如何更好地设计这些聚合?
我是否可以使用城市模型加载公司实体,即使它们存在于不同的聚合中?我想这会很快打破聚合之间的界限。在更新聚合时,处理事务一致性时也会产生混淆。
答案 0 :(得分:3)
您可以创建一个完全不同的对象(它只是一个平面数据结构),它代表视图模型,可以直接从数据库中检索。 Google“精简阅读图层”或“CQRS”。
答案 1 :(得分:1)
Dennis Traub已经指出了如何提高查询性能。这种方法对于查询来说效率更高,但也更笨重,因为您现在需要额外的代码来保持视图模型与聚合同步。
如果您不喜欢这种方法或因其他原因无法使用它,我认为您建议的第一种方法比使用直接对象引用更无效或笨重。假设您在聚合中使用直接对象引用。您如何将这些聚合体持久存储到持久存储中?当您使用数据库时,会想到以下选项:
Company
使用非规范化表(例如,使用MongoDB等文档数据库),则您已经有效地优化了视图查询。但是,您需要执行所有额外工作才能使Company
表与City
,Province
保持同步。效率高但体积大,您可以考虑保留实际视图模型(每个用例一个)。Company
表中的外键来引用相应的City
,Province
等ID。查询Company
时,为了检索填充视图模型所需的City
,Province
等字段,您可以使用超过4个表的JOIN,或者对City
,Province
,...表使用4个独立查询(例如,对外键引用使用延迟加载时)。因此,在第2和第3个选项中,如果让ORM解决方案为您生成数据库映射,则可以节省一些简单的编程工作,但是效率并没有提高。 (JOIN
s可以通过适当的索引进行优化,但正确完成此操作并非易事。)
但是,我想指出,当您通过Id引用并使用程序化应用程序端连接(如您建议的代码中)时,您仍可以完全控制视图模型对象构造和数据库查询。
特别是,城市,省份等的名称通常很少变化,只有很少,它们很容易融入记忆中。因此,您可以广泛使用内存缓存来进行数据库查询 - 甚至可以使用在应用程序启动时从平面文件填充的内存存储库。完成后,要为Company
构建视图模型,只需要对Company
表进行一次数据库调用,并从内存缓存/存储库中检索其他字段,我会考虑效率极高。