您好,我阅读了很多关于 DDD 的内容,遵循此概念的 RIGID 要求之一是仅在“聚合根”类型中使用存储库
public abstract class Repository<T> where T : AggregateRoot
{
public T GetById(long id)
{
}
public void Save( T aggregateRoot)
{
}
}
估值公司有领域模型(子领域)参考。域内部几乎没有业务逻辑,更需要具有 CRUD 操作的存储系统,但仍然可以区分成熟的域。
每个公司都包含要评估的房屋列表,每个房屋都有一组属性和业务逻辑。
PS 在示例中,我特意指定了一个简单的域模型(公共获取,为属性设置)。
базовые классы:
DomainEntity - содержит Id, объект мутабелен
DomainValueObject - НЕ содержит Id объект имутабелен.
public class Company : AggregateRoot
{
public string Name { get; set; }
public List<House> Houses { get; set; }
}
public class House : DomainValueObject<House>
{
public string City { get; set; } // Город
public string District { get; set; } // Район
public string Street { get; set; } // Улица
public string Number { get; set; } // Дом
public int? Year { get; set; } // Год постройки
public string MetroStation { get; set; } // Ближайшая станция метро
public string Geo { get; set; } // гео координаты
public WallMaterial WallMaterial { get; set; }
//бизнес логика......
}
public class WallMaterial : DomainValueObject<WallMaterial>
{
public string Name { get; }
//бизнес логика......
}
在我的示例中,公司是聚合根
阅读这家公司的所有房屋。我将不得不拉动整个公司。
var company= companyRepository.GetById(1);//Тут все зависимости (полный объект Домена) var houses= company.Houses;
DDD 对此有一个答案,对返回的对象执行 Dto(在特殊情况下)。
var housesDto= companyRepository.GetAllHousesDto();
返回的不是House,而是假的,为了符合这个概念——你不能部分地使用聚合,而只能通过聚合的根。houseDto 不是聚合的一部分,它是它的假冒。在这里,遵循这个概念是可以理解的,也不是很痛苦。
- 添加一个新家。
那些。添加新房子,我们通过聚合根进行交互:据我了解,存储库的这种使用是假设的
var company= companyRepository.GetById(1);
company.Houses.Add(newHouse);
companyRepository.Save(company);
那些。为了让我添加房子,我需要从数据库中提取公司,以及所有依赖项,添加房子然后保存。但这不是最优的,不是吗?为什么我需要额外的读取操作?
根据经典(如果您使用 EFcore 作为存储技术),我们这样做。
public async Task<bool> AddHouseInCompanyAsync(long companyId, House house)
{
var efHouse = Mapper.Map<EfHouse>(house);
efHouse.EfCompanyId = companyId;
var res= await _context.Houses.AddAsync(efHouse);
return res.State == EntityState.Added;
}
但这已经是违反 DDD 了,repository 方法使用了 House 部分。
怎样成为?总是通过公司更改对象(即通过聚合根)?还是 DDD 不适合我的任务,使用 CRUD 包装器更好吗?
您误解了聚合根和其他实体的想法。
如果一个实体可以没有根而存在,那么它本身就是一个根。
如果房子可以在没有陪伴的情况下存在,它们也是根。
您的主题的根示例:
房屋地板 - 必须属于房屋根。公寓的居民既可以是子实体(例如,如果他们存在的事实很重要),也可以是根,例如,如果您以更复杂的方式使用它们。
子实体可以与 DTO 类似地完成 - 实际上就是拥有一个子实体并在其中链接到另一个根。那些。company (root) - 有价值的 house (child),其中 child 类型的结构实际上包含对 house 的引用,可能还有一个要存储在数据库中的 id。然后,您可以为这样的孩子添加注释,某种 mb 评级,等等。同时,他们显然不属于这所房子。
有条件的:
首先,再看看你的程序的用例。也许您所在领域的房屋不是公司的一部分,而是独立的实体,它们自己的集合体。
聚合是一个复合对象。例如,在在线商店中,聚合可以是订单实体,其中有单独的商品项目,如果没有订单,这些商品是无用的。商品项目的嵌套通过使用场景及其对应的屏幕形式间接确认。有一个显示所有商品项目的订单编辑表单,但没有用于编辑单独商品项目的表单。
如果您的程序有用于编辑单个房屋的表格,而与公司没有任何联系,那么房屋是专用的聚合根。
聚合可以相互引用,但只能通过标识符。他们不能互相支持。
不需要返回来自存储库的 DTO。通常,DTO 在域之外的某个地方形成,以与应用程序的其他层进行通信。在域本身中,您只有实体和值对象组合成聚合。
关于添加新房子时的额外读取操作。DDD 不是关于优化,而是关于简化架构、简化代码和简化维护。在 DDD 中,存储库从数据库中读取的不是实体的一部分,也不仅仅是实体,而是所有相关实体组合成一个聚合体。
速度损失大吗?不。Fowler 描述了一种模式Remote Façade。使用它的原因是从远程计算机下载数据的特殊性,即存在所谓的延迟。在与服务器建立连接的时候,我们花一点时间,连接建立后,就可以高速接收数据了。因此,读取比我们可能需要的多一点的数据是有用的,它比读取我们需要的两倍或三倍的数据要快。
另外,如果我们想从关系存储切换到文档存储,存储库的接口不会改变,领域层根本不需要改变。所以不要害怕阅读太多。
EF 使用该方法加载相关数据
Include
。假设毕竟,房子是公司的一部分。然后,在加载公司时,需要加载关联的房屋:由于该方法
Include
,不仅可以从数据库加载公司,还可以加载分配给它的房屋。在这段代码中,您可以看到我们正在加载 DTO 并将它们提供给 objectDataMapper
,该对象从它们构建整个聚合,包括House
内部的实体Company
。