【问题标题】:DDD: Aggregate design - Referencing between aggregatesDDD:聚合设计 - 聚合之间的引用
【发布时间】:2014-01-31 13:00:39
【问题描述】:

我对如何设计聚合有疑问。

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

举个例子。一个Company 实体与一个City 相关联,后者属于一个Province,而Province 又属于一个Country

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

ID 引用

如果一个实体需要引用 CityProvinceCountry,他们会通过 ID 这样做(正如 Vaughn Vernon 所建议的那样)。

为了从存储库中获取这些数据,我们需要调用 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);

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

问题

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

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

【问题讨论】:

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


【解决方案1】:

您可以创建一个完全不同的对象(它只是一个平面数据结构)来表示视图模型并且可以直接从数据库中检索。谷歌“Thin Read Layer”或“CQRS”。

【讨论】:

    【解决方案2】:

    Dennis Traub 已经指出了您可以采取哪些措施来提高查询性能。这种方法查询效率更高,但体积也更大,因为您现在需要额外的代码来使视图模型与聚合保持同步。

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-21
      • 1970-01-01
      相关资源
      最近更新 更多