DDD:聚合设计 - 聚合之间的引用

时间:2014-01-31 13:00:39

标签: domain-driven-design repository-pattern unit-of-work aggregateroot onion-architecture

我遇到了如何设计聚合的问题。

我有CompanyCityProvinceCountry个实体。这些中的每一个都需要是其自身聚合的聚合根。 CityProvinceCountry实体在整个系统中使用,并被许多其他实体引用,因此它们不是值对象,也需要在许多不同的场景中访问。所以他们应该有存储库。 CityRepository会有FindById(int)GetAll()GetByProvince(Province)GetByCountry(Country)GetByName(string)等方法。

采用以下示例。 Company实体与City相关联,属于属于Province的{​​{1}}:

Aggregate Roots

现在让我们说我们有一个公司列表页面,列出了一些公司的城市,省份和国家。

ID引用

如果某个实体需要引用CountryCityProvince,则他们会通过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);
}

但据我所知,这些实体不能通过引用引用,因为它们存在于不同的聚合中。

问题

我还能如何更好地设计这些聚合?

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

2 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:1)

Dennis Traub已经指出了如何提高查询性能。这种方法对于查询来说效率更高,但也更笨重,因为您现在需要额外的代码来保持视图模型与聚合同步。

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

  • 如果您正在为Company使用非规范化表(例如,使用MongoDB等文档数据库),则您已经有效地优化了视图查询。但是,您需要执行所有额外工作才能使Company表与CityProvince保持同步。效率高但体积大,您可以考虑保留实际视图模型(每个用例一个)。
  • 如果您使用带有关系数据库的规范化表,则可以使用Company表中的外键来引用相应的CityProvince等ID。查询Company时,为了检索填充视图模型所需的CityProvince等字段,您可以使用超过4个表的JOIN,或者对CityProvince,...表使用4个独立查询(例如,对外键引用使用延迟加载时)。
  • 如果您在非关系数据库中使用规范化表,通常人们会使用与您建议的代码完全相同的application side joins。对于某些数据库,Morphia或Datanucleus等ORM工具可以为您节省一些编程工作,但在幕后,独立查询仍然存在。

因此,在第2和第3个选项中,如果让ORM解决方案为您生成数据库映射,则可以节省一些简单的编程工作,但是效率并没有提高。 (JOIN s可以通过适当的索引进行优化,但正确完成此操作并非易事。)

但是,我想指出,当您通过Id引用并使用程序化应用程序端连接(如您建议的代码中)时,您仍可以完全控制视图模型对象构造和数据库查询。 特别是,城市,省份等的名称通常很少变化,只有很少,它们很容易融入记忆中。因此,您可以广泛使用内存缓存来进行数据库查询 - 甚至可以使用在应用程序启动时从平面文件填充的内存存储库。完成后,要为Company构建视图模型,只需要对Company表进行一次数据库调用,并从内存缓存/存储库中检索其他字段,我会考虑效率极高。